diff --git a/.github/workflows/build-matrix.yaml b/.github/workflows/build-matrix.yaml
index 59601c577f..1d3c4d7e2e 100644
--- a/.github/workflows/build-matrix.yaml
+++ b/.github/workflows/build-matrix.yaml
@@ -3,25 +3,27 @@ name: Build
on:
push:
branches:
- - master
+ - main
pull_request:
branches:
- - master
+ - main
merge_group: {}
jobs:
# Windows
build_windows_clang:
- name: "🖥️ Windows"
+ name: "🖥️ Windows (Clang)"
uses: ./.github/workflows/windows-build-clang.yaml
+ if: github.repository == 'OpenGOAL-Mods/OG-Mod-Base'
with:
cmakePreset: "Release-windows-clang"
cachePrefix: ""
secrets: inherit
build_windows_msvc:
- name: "🖥️ Windows"
+ name: "🖥️ Windows (MSVC)"
uses: ./.github/workflows/windows-build-msvc.yaml
+ if: github.repository == 'OpenGOAL-Mods/OG-Mod-Base'
with:
cmakePreset: "Release-windows-msvc"
cachePrefix: ""
@@ -29,16 +31,18 @@ jobs:
# Linux
build_linux_clang:
- name: "🐧 Linux"
+ name: "🐧 Linux (Clang)"
uses: ./.github/workflows/linux-build-clang.yaml
+ if: github.repository == 'OpenGOAL-Mods/OG-Mod-Base'
with:
cmakePreset: "Release-linux-clang-asan"
cachePrefix: ""
secrets: inherit
build_linux_gcc:
- name: "🐧 Linux"
+ name: "🐧 Linux (GCC)"
uses: ./.github/workflows/linux-build-gcc.yaml
+ if: github.repository == 'OpenGOAL-Mods/OG-Mod-Base'
with:
cmakePreset: "Release-linux-gcc"
cachePrefix: ""
@@ -48,11 +52,12 @@ jobs:
build_macos_intel:
name: "🍎 MacOS"
uses: ./.github/workflows/macos-build.yaml
+ if: github.repository == 'OpenGOAL-Mods/OG-Mod-Base'
with:
cmakePreset: "Release-macos-clang"
cachePrefix: ""
- # Q4 2023 there will hopefully be native arm64 runners
+ # Q4 2023 there will hopefully be native arm64 runners
# https://github.com/github/roadmap/issues/528
# build_macos_arm:
# name: "🍎 MacOS"
diff --git a/.github/workflows/cut-release.yaml b/.github/workflows/cut-release.yaml
new file mode 100644
index 0000000000..3ea4dbf3c0
--- /dev/null
+++ b/.github/workflows/cut-release.yaml
@@ -0,0 +1,34 @@
+name: 🏭 Cut Mod Release
+
+on:
+ workflow_dispatch:
+ inputs:
+ bump:
+ description: 'Semver Bump Type'
+ required: true
+ default: 'patch'
+ type: choice
+ options:
+ - patch
+ - minor
+ - major
+
+permissions:
+ contents: write
+
+jobs:
+ cut_release:
+ name: "Cut Release"
+ uses: open-goal/mod-bundling-tools/.github/workflows/mod-bundler.yml@v1
+ with:
+ semverBump: ${{ inputs.bump }}
+ metadataName: "Test Mod Bundle"
+ metadataDescription: "This is a test mod bundle. 2"
+ metadataSupportedGames: "jak1"
+ metadataAuthors: "barg,vaser"
+ metadataTags: "rng,gameplay-mod"
+ metadataWebsiteUrl: "https://www.example.com/please/change/me"
+ toolingRepo: "OpenGOAL-Mods/OG-Mod-Base"
+ gameAssetsDir: "game/assets"
+ secrets:
+ token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/linux-build-clang.yaml b/.github/workflows/linux-build-clang.yaml
index 7c5ef3be1e..4781e8a799 100644
--- a/.github/workflows/linux-build-clang.yaml
+++ b/.github/workflows/linux-build-clang.yaml
@@ -23,6 +23,9 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ fetch-tags: true
- name: Install Package Dependencies
run: |
@@ -30,7 +33,9 @@ jobs:
sudo apt install build-essential cmake \
clang gcc g++ lcov make nasm libxrandr-dev \
libxinerama-dev libxcursor-dev libpulse-dev \
- libxi-dev zip ninja-build libgl1-mesa-dev libssl-dev
+ libxi-dev zip ninja-build libgl1-mesa-dev libssl-dev \
+ libfreetype6-dev libx11-dev libxrandr-dev libgl1-mesa-dev \
+ libudev-dev libopenal-dev libflac-dev libogg-dev libvorbis-dev
- name: Setup sccache
uses: hendrikmuhs/ccache-action@v1.2.14
@@ -52,10 +57,10 @@ jobs:
- name: Build Project
run: cmake --build build --parallel $((`nproc`))
- - name: Run Tests
- env:
- GTEST_OUTPUT: "xml:opengoal-test-report.xml"
- run: ./test.sh
+# - name: Run Tests
+# env:
+# GTEST_OUTPUT: "xml:opengoal-test-report.xml"
+# run: ./test.sh
- name: Prepare artifacts
if: ${{ inputs.uploadArtifacts }}
diff --git a/.github/workflows/linux-build-gcc.yaml b/.github/workflows/linux-build-gcc.yaml
index 88b7a70af3..57a8af81e5 100644
--- a/.github/workflows/linux-build-gcc.yaml
+++ b/.github/workflows/linux-build-gcc.yaml
@@ -19,6 +19,9 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ fetch-tags: true
- name: Install Package Dependencies
run: |
@@ -54,5 +57,5 @@ jobs:
- name: Build Project
run: cmake --build build --parallel $((`nproc`)) -- -w dupbuild=warn
- - name: Run Tests
- run: ./test.sh
+ # - name: Run Tests
+ # run: ./test.sh
diff --git a/.github/workflows/macos-build-arm.yaml b/.github/workflows/macos-build-arm.yaml
index 032dd27fb2..b84e268a70 100644
--- a/.github/workflows/macos-build-arm.yaml
+++ b/.github/workflows/macos-build-arm.yaml
@@ -19,6 +19,9 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ fetch-tags: true
- name: Set up ARM64 environment
run: sudo softwareupdate --install-rosetta --agree-to-license
diff --git a/.github/workflows/macos-build.yaml b/.github/workflows/macos-build.yaml
index 7792e2b711..eb9108bc46 100644
--- a/.github/workflows/macos-build.yaml
+++ b/.github/workflows/macos-build.yaml
@@ -23,6 +23,9 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ fetch-tags: true
- name: Install Package Dependencies
run: brew install cmake nasm ninja
@@ -47,8 +50,8 @@ jobs:
- name: Build Project
run: cmake --build build --parallel $((`sysctl -n hw.logicalcpu`))
- - name: Run Tests
- run: ./test.sh
+ # - name: Run Tests
+ # run: ./test.sh
- name: Prepare artifacts
if: ${{ inputs.uploadArtifacts }}
diff --git a/.github/workflows/release-pipeline.yaml b/.github/workflows/release-pipeline.yaml
index dcd7a65e50..5d59121140 100644
--- a/.github/workflows/release-pipeline.yaml
+++ b/.github/workflows/release-pipeline.yaml
@@ -17,7 +17,7 @@ permissions:
contents: write
jobs:
- cut_release:
+ cut-release:
name: Cut Release
runs-on: ubuntu-latest
outputs:
@@ -25,7 +25,7 @@ jobs:
steps:
# Docs - https://github.com/mathieudutour/github-tag-action
- name: Bump Version and Push Tag
- if: github.repository == 'open-goal/jak-project'
+ if: github.repository == 'OpenGOAL-Mods/OG-Mod-Base'
id: tag_version
uses: mathieudutour/github-tag-action@v6.2
with:
@@ -45,6 +45,8 @@ jobs:
# Windows
build_windows_clang:
+ needs:
+ - cut-release
name: "🖥️ Windows"
uses: ./.github/workflows/windows-build-clang.yaml
with:
@@ -55,6 +57,8 @@ jobs:
# Linux
build_linux_clang:
+ needs:
+ - cut-release
name: "🐧 Linux"
uses: ./.github/workflows/linux-build-clang.yaml
with:
@@ -65,6 +69,8 @@ jobs:
# macOS
build_macos_intel:
+ needs:
+ - cut-release
name: "🍎 MacOS"
uses: ./.github/workflows/macos-build.yaml
with:
@@ -75,9 +81,8 @@ jobs:
# Upload the Artifacts
upload_artifacts:
- if: github.repository == 'open-goal/jak-project'
needs:
- - cut_release
+ - cut-release
- build_windows_clang
- build_linux_clang
- build_macos_intel
@@ -86,7 +91,8 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
- ref: ${{ needs.cut_release.outputs.new_tag }}
+ fetch-tags: true
+ fetch-depth: 0
- name: Prepare Artifact Folder
run: mkdir -p ./ci-artifacts/final
@@ -104,7 +110,7 @@ jobs:
mkdir -p ./ci-artifacts/linux
./.github/scripts/releases/extract_build_unix.sh ./ci-artifacts/linux ./ci-artifacts/opengoal-linux-static ./
pushd ci-artifacts/linux
- TAG_VAL=${{ needs.cut_release.outputs.new_tag }}
+ TAG_VAL=${{ needs.cut-release.outputs.new_tag }}
tar czf ../final/opengoal-linux-${TAG_VAL}.tar.gz .
popd
chmod +x ./ci-artifacts/opengoal-linux-static/lsp/lsp
@@ -114,7 +120,7 @@ jobs:
run: |
mkdir -p ./ci-artifacts/windows
./.github/scripts/releases/extract_build_windows.sh ./ci-artifacts/windows ./ci-artifacts/opengoal-windows-static ./
- TAG_VAL=${{ needs.cut_release.outputs.new_tag }}
+ TAG_VAL=${{ needs.cut-release.outputs.new_tag }}
7z a -tzip ./ci-artifacts/final/opengoal-windows-${TAG_VAL}.zip ./ci-artifacts/windows/*
cp ./ci-artifacts/opengoal-windows-static/lsp.exe ./ci-artifacts/final/opengoal-lsp-windows-${TAG_VAL}.exe
@@ -123,7 +129,7 @@ jobs:
mkdir -p ./ci-artifacts/macos
./.github/scripts/releases/extract_build_unix.sh ./ci-artifacts/macos ./ci-artifacts/opengoal-macos-static ./
pushd ci-artifacts/macos
- TAG_VAL=${{ needs.cut_release.outputs.new_tag }}
+ TAG_VAL=${{ needs.cut-release.outputs.new_tag }}
tar czf ../final/opengoal-macos-intel-${TAG_VAL}.tar.gz .
popd
chmod +x ./ci-artifacts/opengoal-macos-static/lsp/lsp
@@ -133,12 +139,10 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
- TAG_VAL=${{ needs.cut_release.outputs.new_tag }}
- gh release upload "${TAG_VAL}" ${{ github.WORKSPACE }}/ci-artifacts/final/* --repo open-goal/jak-project --clobber
+ gh release upload ${{ needs.cut-release.outputs.new_tag }} ${{ github.WORKSPACE }}/ci-artifacts/final/* --repo ${{ github.repository }} --clobber
- name: Publish Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
- TAG_VAL=${{ needs.cut_release.outputs.new_tag }}
- gh release edit ${TAG_VAL} --draft=false --repo open-goal/jak-project
+ gh release edit ${{ needs.cut-release.outputs.new_tag }} --draft=false --repo ${{ github.repository }}
diff --git a/.github/workflows/windows-build-clang.yaml b/.github/workflows/windows-build-clang.yaml
index fce72a4ade..6818fd0652 100644
--- a/.github/workflows/windows-build-clang.yaml
+++ b/.github/workflows/windows-build-clang.yaml
@@ -24,6 +24,9 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ fetch-tags: true
- name: Install NASM
# TODO - Simplify this with just the first command once choco 2.0 rolls out everywhere
@@ -60,10 +63,11 @@ jobs:
shell: cmd
run: cmake --build build --parallel %NUMBER_OF_PROCESSORS%
- - name: Run Tests
- env:
- GTEST_OUTPUT: "xml:opengoal-test-report.xml"
- run: ./build/bin/goalc-test.exe --gtest_color=yes --gtest_brief=0 --gtest_filter="-*MANUAL_TEST*"
+# - name: Run Tests
+# timeout-minutes: 10
+# env:
+# GTEST_OUTPUT: "xml:opengoal-test-report.xml"
+# run: ./build/bin/goalc-test.exe --gtest_color=yes --gtest_brief=0 --gtest_filter="-*MANUAL_TEST*"
- name: Upload artifact
uses: actions/upload-artifact@v4
diff --git a/.github/workflows/windows-build-msvc.yaml b/.github/workflows/windows-build-msvc.yaml
index c48f2ba5f7..3277d0f2ba 100644
--- a/.github/workflows/windows-build-msvc.yaml
+++ b/.github/workflows/windows-build-msvc.yaml
@@ -19,6 +19,9 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ fetch-tags: true
- name: Install NASM
# TODO - Simplify this with just the first command once choco 2.0 rolls out everywhere
@@ -56,10 +59,10 @@ jobs:
set CL=/MP
cmake --build build --parallel %NUMBER_OF_PROCESSORS%
- - name: Run Tests
- timeout-minutes: 10
- env:
- GTEST_OUTPUT: "xml:opengoal-test-report.xml"
- run: |
- ./build/bin/goalc-test.exe --gtest_color=yes --gtest_brief=0 --gtest_filter="-*MANUAL_TEST*"
+ # - name: Run Tests
+ # timeout-minutes: 10
+ # env:
+ # GTEST_OUTPUT: "xml:opengoal-test-report.xml"
+ # run: |
+ # ./build/bin/goalc-test.exe --gtest_color=yes --gtest_brief=0 --gtest_filter="-*MANUAL_TEST*"
diff --git a/.gitignore b/.gitignore
index 3dc67daa45..55d7ec870d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -57,19 +57,33 @@ imgui.ini
node_modules/
# texture replacements
-custom_assets/jak1/texture_replacements/*
-custom_assets/jak2/texture_replacements/*
-custom_assets/jak3/texture_replacements/*
+# custom_assets/jak1/texture_replacements/*
+# custom_assets/jak2/texture_replacements/*
+# custom_assets/jak3/texture_replacements/*
# merc replacements
-custom_assets/jak1/merc_replacements/*
-custom_assets/jak2/merc_replacements/*
-custom_assets/jak3/merc_replacements/*
+# custom_assets/jak1/merc_replacements/*
+# custom_assets/jak2/merc_replacements/*
+# custom_assets/jak3/merc_replacements/*
# generated cmake files
svnrev.h
common/versions/revision.h
ci-artifacts/
+!out/
+out/*
+!out/build/
+out/build/*
+!out/build/Release
+out/build/Release/*
+!out/build/Release/bin/
+out/build/Release/bin/*
+!out/build/Release/bin/SDL2.dll
+!out/build/Release/bin/openal32.dll
+!out/build/Release/bin/extractor.exe
+!out/build/Release/bin/decompiler.exe
+!out/build/Release/bin/gk.exe
+!out/build/Release/bin/goalc.exe
__pycache__/
# sqlite stuff
@@ -82,7 +96,11 @@ __pycache__/
# docs
/jak1-*.json
/jak2-*.json
+
/TODO.md
unifont-15.0.03.ttf
+.vscode/settings.json
+out/jak2/fr3.rar
+
*.diff
-goalc-report.html
\ No newline at end of file
+goalc-report.html
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 545e13bd62..57ed6a2632 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -8,5 +8,8 @@
"editor.wordBasedSuggestions": "matchingDocuments",
"editor.snippetSuggestions": "top"
},
- "cmake.configureOnOpen": false
+ "cmake.configureOnOpen": false,
+ "files.associations": {
+ "optional": "cpp"
+ }
}
diff --git a/CMakeLists.txt b/CMakeLists.txt
index bd099d7888..6b17369f03 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,5 +1,5 @@
# Top Level CMakeLists.txt
-cmake_minimum_required(VERSION 3.10)
+cmake_minimum_required(VERSION 3.12)
set(CMAKE_CXX_STANDARD 20)
project(jak)
@@ -31,6 +31,7 @@ if (EXISTS "${CMAKE_CURRENT_BINARY_DIR}/compile_commands.json" )
"${PROJECT_SOURCE_DIR}/build/compile_commands.json")
endif()
+
# Setup compiler flags
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
message(STATUS "Clang Detected - Setting Defaults")
diff --git a/README.md b/README.md
index 413fb8fe7a..453c49e318 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,50 @@
+# OpenGoal-Mod-Base
+Serves as a base template for openGOAL mods that will be supported via [OG-ModLauncher](https://github.com/OpenGOAL-Mods/OG-ModLauncher).
+
+- Please ensure you are not committing copyrighted material to your repo (the `.gitignore` should help prevent this).
+- Generally speaking you should only be updating certain directories/files:
+ - GOAL code (`/goal_src`)
+ - Assets specific to the PC Port (`/game/assets/jak1/`, `/custom_assets/`)
+ - The executable binaries (`/out/build/Release/goalc.exe`, `/out/build/Release/gk.exe`, `/out/build/Release/extractor.exe`)
+ - Decompiler config (`/decompiler/config`)
+
+## Custom Navmesh Implementation and Example
+
+LuminarLight made changes that allow placing custom navmesh into Jak 1 levels. This will hopefully become useless one day, if proper navmesh support is ever added to OpenGOAL.
+
+The navmesh system in Jak II is more advanced, I haven't managed to figure it out yet.
+
+### Getting Started
+
+Please keep in mind that you are expected to be familiar with custom levels and GOAL. Still, I tried to make things as understandable as possible.
+
+I would recommend copying an existing navmesh as a start. You can use the inspect method I made. The actor whose navmesh you want to copy must be loaded. Example:
+`(inspect (-> (the-as entity-actor (entity-by-name "snow-bunny-55")) nav-mesh))`
+
+You should change the origin and bounds, depending on where you want to place your navmesh.
+
+I usually just remove the nodes, because I do not understand it and things seem fine without it. But keep in mind that every navmesh that is in the game has at least one node.
+
+We do not understand route, but it is needed - otherwise game will crash. If you copy an existing navmesh, the route data is copied correctly. But since we don't understand it, for fully custom navmesh we can never have proper route data. Correct route data is essential if you want to take advantage of gap triangles (where enemies jump).
+
+You can make multiple enemies use the same navmesh. To do this, create the navmesh through code for the first actor, like in the example. And for the other actors, add a lump that tells the game to use another actor's navmesh. Reference is by aid. Example: `"nav-mesh-actor": ["uint32", 40000]`. Tip: You can do the same thing with paths, using the `path-actor` lump.
+
+If the game crashes when you approach a custom navmesh, make sure you added `:custom-hacky? #t` to your custom navmesh definition. If that is there, then check if the actor has a path. It needs a path.
+
+If something is still unclear, please look at the code. I added a lot of comments.
+
+### Final Words
+
+I am not an expert at decompiling, so my methods were not the most efficient. But with a lot of time, I managed to figure things out. There are probably people who could do this a lot better than me. Hopefully it will happen.
+
+Also, I know my inspect method is not perfect. But it is very tedious to write such a thing, so I just included what we really need. And I think the nodes part could use a cleanup.
+
+I am happy if anyone finds this useful. But I have a request: If you learn more about navmeshes, especially things that would benefit other modders as well, please let me know. And maybe we will add it to this branch.
+
+*~~Luminar Light*
+
+---
+
diff --git a/common/custom_data/pack_helpers.cpp b/common/custom_data/pack_helpers.cpp
index 6bed346832..68e0910aa6 100644
--- a/common/custom_data/pack_helpers.cpp
+++ b/common/custom_data/pack_helpers.cpp
@@ -19,7 +19,8 @@ std::pair position_to_cluster_and_offset(float in) {
float recovered = ((float)cluster_cell + ((float)offset / UINT16_MAX)) * kClusterSize;
float diff = std::fabs(recovered - in);
- ASSERT(diff < 7);
+ // mod-base-change made this bigger
+ ASSERT(diff < 32587);
ASSERT(cluster_cell >= 0);
ASSERT(cluster_cell < UINT16_MAX);
return {cluster_cell, offset};
diff --git a/common/formatter/formatter.h b/common/formatter/formatter.h
index 58484cedc4..bb9e3aa0ba 100644
--- a/common/formatter/formatter.h
+++ b/common/formatter/formatter.h
@@ -6,7 +6,6 @@
#include "tree_sitter/api.h"
namespace formatter {
-
struct TreeSitterParserDeleter {
void operator()(TSParser* ptr) const { ts_parser_delete(ptr); }
};
diff --git a/common/util/FileUtil.cpp b/common/util/FileUtil.cpp
index f150801cc1..e81b9e683e 100644
--- a/common/util/FileUtil.cpp
+++ b/common/util/FileUtil.cpp
@@ -1,828 +1,846 @@
-/*!
- * @file FileUtil.cpp
- * Utility functions for reading and writing files.
- */
-
-#include "FileUtil.h"
-
-#include
-#include /* defines FILENAME_MAX */
-#include
-#include
-#include
-#include
-
-#include "BinaryWriter.h"
-
-#include "common/common_types.h"
-#include "common/util/BinaryReader.h"
-#include "common/util/string_util.h"
-#include "common/util/unicode_util.h"
-
-// This disables the use of PCLMULQDQ which is probably ok, but let's just be safe and disable it
-// because nobody will care if png compression is 10% slower.
-#define FPNG_NO_SSE 1
-#include "fmt/core.h"
-#include "third-party/fpng/fpng.cpp"
-#include "third-party/fpng/fpng.h"
-#include "third-party/lzokay/lzokay.hpp"
-
-#ifdef _WIN32
-#define NOMINMAX
-#define WIN32_LEAN_AND_MEAN
-#include
-#else
-#include
-#include
-#endif
-#include "common/log/log.h"
-#include "common/util/Assert.h"
-
-#ifdef __APPLE__
-#include
-#include
-
-#include "mach-o/dyld.h"
-#endif
-
-namespace file_util {
-fs::path get_user_home_dir() {
-#ifdef _WIN32
- // NOTE - on older systems, this may case issues if it cannot be found!
- std::string home_dir = get_env("USERPROFILE");
- return fs::path(home_dir);
-#else
- std::string home_dir = get_env("HOME");
- return fs::path(home_dir);
-#endif
-}
-
-struct {
- bool initialized = false;
- fs::path path_to_data_folder;
- fs::path user_config_dir_override = "";
- // by default - if the config dir is overridden, we don't use the default save location
- bool use_overridden_config_dir_for_saves = true;
-} g_file_path_info;
-
-fs::path get_user_config_dir() {
- fs::path config_base_path;
-#ifdef _WIN32
- auto config_base_dir = get_env("APPDATA");
- config_base_path = fs::path(config_base_dir);
-#elif __linux
- // Docs - https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
- // Prefer XDG_CONFIG_HOME if available
- auto config_base_dir = get_env("XDG_CONFIG_HOME");
- if (config_base_dir.empty()) {
- config_base_path = get_user_home_dir() / ".config";
- } else {
- config_base_path = fs::path(config_base_dir);
- }
-#elif __APPLE__
- auto config_base_dir = get_env("HOME");
- config_base_path = fs::path(config_base_dir) / "Library" / "Application Support";
-#endif
- return config_base_path / "OpenGOAL";
-}
-
-fs::path get_user_settings_dir(GameVersion game_version) {
- auto game_version_name = game_version_names[game_version];
- auto config_dir = get_user_config_dir();
- if (!g_file_path_info.user_config_dir_override.empty()) {
- config_dir = g_file_path_info.user_config_dir_override / "OpenGOAL";
- }
- return config_dir / game_version_name / "settings";
-}
-
-fs::path get_user_memcard_dir(GameVersion game_version) {
- auto game_version_name = game_version_names[game_version];
- auto config_dir = get_user_config_dir();
- if (!g_file_path_info.user_config_dir_override.empty() &&
- g_file_path_info.use_overridden_config_dir_for_saves) {
- config_dir = g_file_path_info.user_config_dir_override / "OpenGOAL";
- }
- return config_dir / game_version_name / "saves";
-}
-
-fs::path get_user_screenshots_dir(GameVersion game_version) {
- auto game_version_name = game_version_names[game_version];
- auto config_dir = get_user_config_dir();
- if (!g_file_path_info.user_config_dir_override.empty()) {
- config_dir = g_file_path_info.user_config_dir_override / "OpenGOAL";
- }
- return config_dir / game_version_name / "screenshots";
-}
-
-fs::path get_user_misc_dir(GameVersion game_version) {
- auto game_version_name = game_version_names[game_version];
- auto config_dir = get_user_config_dir();
- if (!g_file_path_info.user_config_dir_override.empty()) {
- config_dir = g_file_path_info.user_config_dir_override / "OpenGOAL";
- }
- return config_dir / game_version_name / "misc";
-}
-
-fs::path get_user_features_dir(GameVersion game_version) {
- auto game_version_name = game_version_names[game_version];
- auto config_dir = get_user_config_dir();
- if (!g_file_path_info.user_config_dir_override.empty()) {
- config_dir = g_file_path_info.user_config_dir_override / "OpenGOAL";
- }
- auto path = config_dir / game_version_name / "features";
- file_util::create_dir_if_needed(path);
- return path;
-}
-
-fs::path g_iso_data_directory = "";
-
-/*!
- * Get the path to the current executable.
- */
-std::string get_current_executable_path() {
-#ifdef _WIN32
- // NOTE - MAX_PATH is kinda wrong here as you can have a path longer than 260 in windows
- wchar_t path[MAX_PATH];
- GetModuleFileNameW(NULL, path, MAX_PATH);
- std::string file_path = wide_string_to_utf8_string(path);
- if (file_path.rfind("\\\\?\\", 0) == 0) {
- return file_path.substr(4);
- }
- return file_path;
-#elif __linux
- char buffer[FILENAME_MAX + 1];
- auto len = readlink("/proc/self/exe", buffer,
- FILENAME_MAX); // /proc/self acts like a "virtual folder" containing
- // information about the current process
- buffer[len] = '\0';
- return std::string(buffer);
-#elif __APPLE__
- char buffer[PATH_MAX];
- uint32_t bufsize = sizeof(buffer);
- if (_NSGetExecutablePath(buffer, &bufsize) != 0) {
- lg::warn("Could not get executable path, trying with _NSGetArgv()[0] instead.");
- auto argv = *_NSGetArgv();
- return std::string(argv[0]);
- }
- return std::string(buffer);
-#endif
-}
-
-std::optional try_get_project_path_from_path(const std::string& path) {
- std::string::size_type pos =
- std::string(path).rfind("jak-project"); // Strip file path down to /jak-project/ directory
- if (pos == std::string::npos) {
- return {};
- }
- return std::string(path).substr(
- 0, pos + 11); // + 12 to include "/jak-project" in the returned filepath
-}
-
-/*!
- * See if the current executable is somewhere in jak-project/. If so, return the path to jak-project
- */
-std::optional try_get_jak_project_path() {
- return try_get_project_path_from_path(get_current_executable_path());
-}
-
-std::optional try_get_data_dir(bool skip_logs) {
- fs::path my_path = get_current_executable_path();
- if (!skip_logs) {
- lg::debug("Current executable directory - {}", my_path.string());
- }
- auto data_dir = my_path.parent_path() / "data";
- if (fs::exists(data_dir) && fs::is_directory(data_dir)) {
- return std::make_optional(data_dir);
- } else {
- return {};
- }
-}
-
-bool setup_project_path(std::optional project_path_override, bool skip_logs) {
- if (g_file_path_info.initialized) {
- return true;
- }
-
- if (project_path_override) {
- g_file_path_info.path_to_data_folder = fs::absolute(project_path_override.value());
- g_file_path_info.initialized = true;
- if (!skip_logs) {
- lg::debug("Using explicitly set project path: {}",
- g_file_path_info.path_to_data_folder.string());
- }
- return true;
- }
-
- auto data_path = try_get_data_dir(skip_logs);
- if (data_path) {
- g_file_path_info.path_to_data_folder = *data_path;
- g_file_path_info.initialized = true;
- if (!skip_logs) {
- lg::debug("Using data path: {}", data_path->string());
- }
- return true;
- }
-
- auto development_repo_path = try_get_jak_project_path();
- if (development_repo_path) {
- g_file_path_info.path_to_data_folder = *development_repo_path;
- g_file_path_info.initialized = true;
- if (!skip_logs) {
- lg::debug("Using development repo path: {}", *development_repo_path);
- }
- return true;
- }
-
- lg::error("Failed to initialize project path.");
- return false;
-}
-
-void override_user_config_dir(fs::path user_config_dir_override,
- bool use_overridden_config_dir_for_saves) {
- g_file_path_info.user_config_dir_override = user_config_dir_override;
- g_file_path_info.use_overridden_config_dir_for_saves = use_overridden_config_dir_for_saves;
-}
-
-fs::path get_jak_project_dir() {
- ASSERT(g_file_path_info.initialized);
- return g_file_path_info.path_to_data_folder;
-}
-
-fs::path get_iso_dir_for_game(GameVersion game_version) {
- if (!g_iso_data_directory.empty()) {
- return g_iso_data_directory;
- }
- // Find the location based on the game version
- std::string expected_subdir = "jak1";
- if (game_version == GameVersion::Jak2) {
- expected_subdir = "jak2";
- } else if (game_version == GameVersion::Jak3) {
- expected_subdir = "jak3";
- }
- const auto temp_dir = get_jak_project_dir() / "iso_data" / expected_subdir;
- if (fs::exists(temp_dir)) {
- g_iso_data_directory = temp_dir;
- }
- return g_iso_data_directory;
-}
-
-void set_iso_data_dir(const fs::path& directory) {
- g_iso_data_directory = directory;
-}
-
-std::string get_file_path(const std::vector& input) {
- // TODO - clean this behaviour up, it causes unexpected behaviour when working with files
- // the project path should be explicitly provided by whatever if needed
- // TEMP HACK
- // - if the provided path is absolute, don't add the project path
- if (input.size() == 1 && fs::path(input.at(0)).is_absolute()) {
- return input.at(0);
- }
-
- auto current_path = file_util::get_jak_project_dir();
- for (auto& str : input) {
- current_path /= str;
- }
-
- return current_path.string();
-}
-
-bool create_dir_if_needed(const fs::path& path) {
- if (!fs::is_directory(path)) {
- fs::create_directories(path);
- return true;
- }
- return false;
-}
-
-// TODO - explodes if the file path is invalid
-bool create_dir_if_needed_for_file(const std::string& path) {
- return create_dir_if_needed_for_file(fs::path(path));
-}
-
-// TODO - explodes if the file path is invalid
-bool create_dir_if_needed_for_file(const fs::path& path) {
- return fs::create_directories(path.parent_path());
-}
-
-void write_binary_file(const fs::path& name, const void* data, size_t size) {
- FILE* fp = file_util::open_file(name.string().c_str(), "wb");
- if (!fp) {
- throw std::runtime_error("couldn't open file " + name.string());
- }
-
- if (size == 0) {
- // nothing to write, just 'touch' the file
- fclose(fp);
- return;
- }
-
- if (fwrite(data, size, 1, fp) != 1) {
- fclose(fp);
- throw std::runtime_error("couldn't write file " + name.string());
- }
-
- fclose(fp);
-}
-
-void write_binary_file(const std::string& name, const void* data, size_t size) {
- write_binary_file(fs::path(name), data, size);
-}
-
-void write_rgba_png(const fs::path& name, void* data, int w, int h) {
- auto flags = 0;
-
- auto ok = fpng::fpng_encode_image_to_file(name.string().c_str(), data, w, h, 4, flags);
-
- if (!ok) {
- throw std::runtime_error("couldn't save png file " + name.string());
- }
-}
-
-void write_text_file(const std::string& file_name, const std::string& text) {
- write_text_file(fs::path(file_name), text);
-}
-
-void write_text_file(const fs::path& file_name, const std::string& text) {
- FILE* fp = file_util::open_file(file_name.string().c_str(), "w");
- if (!fp) {
- lg::error("Failed to fopen {}\n", file_name.string());
- throw std::runtime_error("Failed to open file");
- }
- fprintf(fp, "%s\n", text.c_str());
- fclose(fp);
-}
-std::vector read_binary_file(const std::string& filename) {
- return read_binary_file(fs::path(filename));
-}
-
-std::vector read_binary_file(const fs::path& path) {
- // make sure file exists and isn't a directory
-
- auto status = fs::status(path);
-
- if (!fs::exists(status)) {
- throw std::runtime_error(
- fmt::format("File {} cannot be opened: does not exist.", path.string()));
- }
-
- if (status.type() != fs::file_type::regular && status.type() != fs::file_type::symlink) {
- throw std::runtime_error(
- fmt::format("File {} cannot be opened: not a regular file or symlink.", path.string()));
- }
-
- auto fp = file_util::open_file(path.string().c_str(), "rb");
- if (!fp)
- throw std::runtime_error("File " + path.string() +
- " cannot be opened: " + std::string(strerror(errno)));
- fseek(fp, 0, SEEK_END);
- auto len = ftell(fp);
- if (len == 0) {
- fclose(fp);
- return {};
- }
- rewind(fp);
-
- std::vector data;
- data.resize(len);
-
- if (fread(data.data(), len, 1, fp) != 1) {
- fclose(fp);
- throw std::runtime_error("File " + path.string() + " cannot be read");
- }
- fclose(fp);
-
- return data;
-}
-
-std::string read_text_file(const fs::path& path) {
- fs::ifstream file(path);
- if (!file.good()) {
- throw std::runtime_error("couldn't open " + path.string());
- }
- std::stringstream ss;
- ss << file.rdbuf();
- return ss.str();
-}
-
-std::string read_text_file(const std::string& path) {
- return read_text_file(fs::path(path));
-}
-
-bool is_printable_char(char c) {
- return c >= ' ' && c <= '~';
-}
-
-std::string combine_path(const std::string& parent, const std::string& child) {
- return parent + "/" + child;
-}
-
-bool file_exists(const std::string& path) {
- return fs::exists(path);
-}
-
-std::string base_name(const std::string& filename) {
- size_t pos = 0;
- ASSERT(!filename.empty());
- for (size_t i = filename.size() - 1; i-- > 0;) {
- if (filename.at(i) == '/' || filename.at(i) == '\\') {
- pos = (i + 1);
- break;
- }
- }
- return filename.substr(pos);
-}
-
-std::string base_name_no_ext(const std::string& filename) {
- size_t pos = 0;
- ASSERT(!filename.empty());
- for (size_t i = filename.size() - 1; i-- > 0;) {
- if (filename.at(i) == '/' || filename.at(i) == '\\') {
- pos = (i + 1);
- break;
- }
- }
- std::string file_name = filename.substr(pos);
- return file_name.substr(0, file_name.find_last_of('.'));
- ;
-}
-
-std::string split_path_at(const fs::path& path, const std::vector& folders) {
- std::string split_str = "";
- for (const auto& folder : folders) {
-#ifdef _WIN32
- split_str += folder + "\\";
-#else
- split_str += folder + "/";
-#endif
- }
- const auto& path_str = path.string();
- return path_str.substr(path_str.find(split_str) + split_str.length());
-}
-
-std::string convert_to_unix_path_separators(const std::string& path) {
-#ifdef _WIN32
- std::string copy = path;
- std::replace(copy.begin(), copy.end(), '\\', '/');
- return copy;
-#else
- return path;
-#endif
-}
-
-/*!
- * Convert an animation name to ISO name.
- * The animation name is a bunch of dash separated words.
- * The resulting ISO name has the same first two chars as the animation name, and one char from each
- * remaining word. Once there are no more words but remaining chars in the ISO name, the ith extra
- * char is the i+1 th char of the last word. A word ending in a number (or just a number) is turned
- * into the number. The word "resolution" becomes z. The word "accept" becomes y. The word "reject"
- * becomes n. Other words become the first char of the word. The result is uppercased and the file
- * extension is STR Examples (animation name and disc file name, not ISO name):
- * green-sagecage-outro-beat-boss-enough-cells -> GRSOBBEC.STR
- * swamp-tetherrock-swamprockexplode-4 -> SWTS4.STR
- * minershort-resolution-1-orbs -> MIZ1ORBS.STR
- */
-void ISONameFromAnimationName(char* dst, const char* src) {
- // The Animation Name is a bunch of words separated by dashes
-
- // copy first two chars of the first word exactly
- dst[0] = src[0];
- dst[1] = src[1];
- s32 i = 2; // 2 chars added to dst.
-
- // skip ahead to the first dash (or \0 if there's no dashes)
- const char* src_ptr = src;
- while (*src_ptr && *src_ptr != '-') {
- src_ptr++;
- }
-
- // the points to the next dash (or \0 if there's none).
- const char* next_ptr = src_ptr;
- if (*src_ptr) {
- // loop over words (next_ptr points to dash before word, i counts chars in dest)
- while (src_ptr = next_ptr + 1, i < 8) {
- // scan next_ptr forward to next dash
- next_ptr = src_ptr;
- while (*next_ptr && *next_ptr != '-') {
- next_ptr++;
- }
-
- // there's no next word, so break (the current word will be handled there)
- if (!*next_ptr)
- break;
-
- // add a char for the current word:
- char char_to_add;
- if (next_ptr[-1] < '0' || next_ptr[-1] > '9') {
- // word doesn't end in a number.
-
- // some special case words map to special letters (likely to avoid animation name conflicts)
- if (next_ptr - src_ptr == 10 && !memcmp(src_ptr, "resolution", 10)) {
- // NOTE : jak 2 also allows "res" here but that doesn't work properly.
- char_to_add = 'z';
- } else if (next_ptr - src_ptr == 6 && !memcmp(src_ptr, "accept", 6)) {
- char_to_add = 'y';
- } else if (next_ptr - src_ptr == 6 && !memcmp(src_ptr, "reject", 6)) {
- char_to_add = 'n';
- } else if (next_ptr - src_ptr == 5 && !memcmp(src_ptr, "keira", 5)) {
- // NOTE : this was added in jak 2. it's safe to use in jak 1 since she was referred to as
- // "assistant" there
- char_to_add = 'i';
- } else {
- // not a special case, just take the first letter.
- char_to_add = *src_ptr;
- }
- } else {
- // the current word ends in a number, just use this number (I think usually the whole word
- // is just a number)
- char_to_add = next_ptr[-1];
- }
-
- dst[i++] = char_to_add;
- }
-
- // here we ran out of room in dest, or words in source.
- // if there's still room in dest and chars in source, just add them
- while (*src_ptr && (i < 8)) {
- dst[i] = *src_ptr;
- src_ptr++;
- i++;
- }
- }
-
- // pad with spaces (for ISO Name)
- while (i < 8) {
- dst[i++] = ' ';
- }
-
- // upper case
- for (i = 0; i < 8; i++) {
- if (dst[i] >= 'a' && dst[i] <= 'z') {
- dst[i] -= 0x20;
- }
- }
-
- // append file extension
- strcpy(dst + 8, "STR");
-}
-
-/*!
- * Convert file name to "ISO Name"
- * ISO names are upper case and 12 bytes long.
- * xxxxxxxxyyy0
- *
- * x - uppercase letter of file name, or space
- * y - uppercase letter of file extension, or space
- * 0 - null terminator (\0, not the character zero)
- */
-void MakeISOName(char* dst, const char* src) {
- int i = 0;
- const char* src_ptr = src;
- char* dst_ptr = dst;
-
- // copy name and upper case
- while ((i < 8) && (*src_ptr) && (*src_ptr != '.')) {
- char c = *src_ptr;
- src_ptr++;
- if (('`' < c) && (c < '{')) { // lower case
- c -= 0x20;
- }
- *dst_ptr = c;
- dst_ptr++;
- i++;
- }
-
- // pad out name with spaces
- while (i < 8) {
- *dst_ptr = ' ';
- dst_ptr++;
- i++;
- }
-
- // increment past period
- if (*src_ptr == '.')
- src_ptr++;
-
- // same for extension
- while (i < 11 && (*src_ptr)) {
- char c = *src_ptr;
- src_ptr++;
- if (('`' < c) && (c < '{')) { // lower case
- c -= 0x20;
- }
- *dst_ptr = c;
- dst_ptr++;
- i++;
- }
-
- while (i < 11) {
- *dst_ptr = ' ';
- dst_ptr++;
- i++;
- }
- *dst_ptr = 0;
-}
-
-void assert_file_exists(const char* path, const char* error_message) {
- if (!fs::exists(path)) {
- ASSERT_MSG(false, fmt::format("File {} was not found: {}", path, error_message));
- }
-}
-
-/*!
- * Check if the given DGO header (or entire file) is compressed.
- */
-bool dgo_header_is_compressed(const std::vector& data) {
- const char compressed_header[] = "oZlB";
- bool is_compressed = true;
- for (int i = 0; i < 4; i++) {
- if (compressed_header[i] != data.at(i)) {
- is_compressed = false;
- }
- }
- return is_compressed;
-}
-
-/*!
- * Decompress a DGO. Resulting data will start at the DGO header.
- */
-std::vector decompress_dgo(const std::vector& data_in) {
- constexpr int MAX_CHUNK_SIZE = 0x8000;
- BinaryReader compressed_reader(data_in);
- // seek past oZlB
- compressed_reader.ffwd(4);
- std::size_t decompressed_size = compressed_reader.read();
- std::vector decompressed_data;
- decompressed_data.resize(decompressed_size);
- size_t output_offset = 0;
- while (true) {
- // seek past alignment bytes and read the next chunk size
- uint32_t chunk_size = 0;
- while (!chunk_size) {
- chunk_size = compressed_reader.read();
- }
-
- if (chunk_size < MAX_CHUNK_SIZE) {
- std::size_t bytes_written = 0;
- lzokay::EResult ok = lzokay::decompress(
- compressed_reader.here(), chunk_size, decompressed_data.data() + output_offset,
- decompressed_data.size() - output_offset, bytes_written);
- ASSERT(ok == lzokay::EResult::Success);
- compressed_reader.ffwd(chunk_size);
- output_offset += bytes_written;
- } else {
- // nope - sometimes chunk_size is bigger than MAX, but we should still use max.
- // ASSERT(chunk_size == MAX_CHUNK_SIZE);
- memcpy(decompressed_data.data() + output_offset, compressed_reader.here(), MAX_CHUNK_SIZE);
- compressed_reader.ffwd(MAX_CHUNK_SIZE);
- output_offset += MAX_CHUNK_SIZE;
- }
-
- if (output_offset >= decompressed_size)
- break;
- while (compressed_reader.get_seek() % 4) {
- compressed_reader.ffwd(1);
- }
- }
-
- return decompressed_data;
-}
-
-FILE* open_file(const fs::path& path, const std::string& mode) {
-#ifdef _WIN32
- return _wfopen(path.wstring().c_str(), std::wstring(mode.begin(), mode.end()).c_str());
-#else
- return fopen(path.string().c_str(), mode.c_str());
-#endif
-}
-
-std::vector find_files_in_dir(const fs::path& dir, const std::regex& pattern) {
- std::vector files = {};
- for (auto& p : fs::directory_iterator(dir)) {
- if (p.is_regular_file()) {
- if (std::regex_match(p.path().filename().string(), pattern)) {
- files.push_back(p.path());
- }
- }
- }
- return files;
-}
-
-std::vector find_files_recursively(const fs::path& base_dir, const std::regex& pattern) {
- std::vector files = {};
- if (!fs::exists(base_dir)) {
- return files;
- }
- for (auto& p : fs::recursive_directory_iterator(base_dir)) {
- if (p.is_regular_file()) {
- if (std::regex_match(p.path().filename().string(), pattern)) {
- files.push_back(p.path());
- }
- }
- }
- return files;
-}
-
-std::vector find_directories_in_dir(const fs::path& base_dir) {
- std::vector dirs = {};
- for (auto& p : fs::recursive_directory_iterator(base_dir)) {
- if (p.is_directory()) {
- dirs.push_back(p.path());
- }
- }
- return dirs;
-}
-
-std::vector sort_filepaths(const std::vector& paths, const bool aescending) {
- std::vector paths_as_strings = {};
- for (const auto& path : paths) {
- paths_as_strings.push_back(path.string());
- }
- std::sort(paths_as_strings.begin(), paths_as_strings.end(),
- [aescending](const std::string& a, const std::string& b) {
- if (aescending) {
- return a < b;
- } else {
- return a > b;
- }
- });
- std::vector sorted_paths = {};
- for (const auto& path : paths_as_strings) {
- sorted_paths.push_back(fs::path(path));
- }
- return sorted_paths;
-}
-
-void copy_file(const fs::path& src, const fs::path& dst) {
- // Check that the src path exists
- if (!fs::exists(src)) {
- throw std::runtime_error(fmt::format("Cannot copy '{}', path does not exist", src.string()));
- }
- // Ensure the directory can be copied into
- if (!fs::exists(dst.parent_path()) && !create_dir_if_needed_for_file(dst)) {
- throw std::runtime_error(fmt::format(
- "Cannot copy '{}', couldn't make directory to copy into '{}'", src.string(), dst.string()));
- }
- fs::copy_file(src, dst, fs::copy_options::overwrite_existing);
-}
-
-std::string make_screenshot_filepath(const GameVersion game_version, const std::string& name) {
- std::string file_name;
- if (name.empty()) {
- file_name = fmt::format("{}.png", str_util::current_local_timestamp_no_colons());
- } else {
- file_name = fmt::format("{}.png", name);
- }
- const auto file_path = get_user_screenshots_dir(game_version) / file_name;
- file_util::create_dir_if_needed_for_file(file_path);
- return file_path.string();
-}
-
-std::string get_majority_file_line_endings(const std::string& file_contents) {
- size_t lf_count = 0;
- size_t crlf_count = 0;
-
- for (size_t i = 0; i < file_contents.size(); ++i) {
- if (file_contents[i] == '\n') {
- if (i > 0 && file_contents[i - 1] == '\r') {
- crlf_count++;
- } else {
- lf_count++;
- }
- }
- }
-
- if (crlf_count > lf_count) {
- return "\r\n";
- }
- return "\n";
-}
-
-std::pair get_majority_file_line_endings_and_count(
- const std::string& file_contents) {
- size_t lf_count = 0;
- size_t crlf_count = 0;
-
- for (size_t i = 0; i < file_contents.size(); ++i) {
- if (file_contents[i] == '\n') {
- if (i > 0 && file_contents[i - 1] == '\r') {
- crlf_count++;
- } else {
- lf_count++;
- }
- }
- }
-
- if (crlf_count > lf_count) {
- return {lf_count + crlf_count, "\r\n"};
- }
- return {lf_count + crlf_count, "\n"};
-}
-
-bool is_dir_in_dir(const fs::path& parent, const fs::path& child) {
- // Check if the parent path is a prefix of the child path
- return child.has_parent_path() && child.parent_path().lexically_relative(parent) == fs::path(".");
-}
-
-} // namespace file_util
+/*!
+ * @file FileUtil.cpp
+ * Utility functions for reading and writing files.
+ */
+
+#include "FileUtil.h"
+
+#include
+#include /* defines FILENAME_MAX */
+#include
+#include
+#include
+#include
+
+#include "BinaryWriter.h"
+
+#include "common/common_types.h"
+#include "common/util/BinaryReader.h"
+#include "common/util/string_util.h"
+#include "common/util/unicode_util.h"
+
+// This disables the use of PCLMULQDQ which is probably ok, but let's just be safe and disable it
+// because nobody will care if png compression is 10% slower.
+#define FPNG_NO_SSE 1
+#include "fmt/core.h"
+#include "third-party/fpng/fpng.cpp"
+#include "third-party/fpng/fpng.h"
+#include "third-party/lzokay/lzokay.hpp"
+
+#ifdef _WIN32
+#define NOMINMAX
+#define WIN32_LEAN_AND_MEAN
+#include
+#else
+#include
+#include
+#endif
+#include "common/log/log.h"
+#include "common/util/Assert.h"
+
+#ifdef __APPLE__
+#include
+#include
+
+#include "mach-o/dyld.h"
+#endif
+
+namespace file_util {
+fs::path get_user_home_dir() {
+#ifdef _WIN32
+ // NOTE - on older systems, this may case issues if it cannot be found!
+ std::string home_dir = get_env("USERPROFILE");
+ return fs::path(home_dir);
+#else
+ std::string home_dir = get_env("HOME");
+ return fs::path(home_dir);
+#endif
+}
+
+struct {
+ bool initialized = false;
+ fs::path path_to_data_folder;
+ fs::path user_config_dir_override = "";
+ // by default - if the config dir is overridden, we don't use the default save location
+ bool use_overridden_config_dir_for_saves = true;
+} g_file_path_info;
+
+fs::path get_user_config_dir() {
+ fs::path config_base_path;
+#ifdef _WIN32
+ auto config_base_dir = get_env("APPDATA");
+ config_base_path = fs::path(config_base_dir);
+#elif __linux
+ // Docs - https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
+ // Prefer XDG_CONFIG_HOME if available
+ auto config_base_dir = get_env("XDG_CONFIG_HOME");
+ if (config_base_dir.empty()) {
+ config_base_path = get_user_home_dir() / ".config";
+ } else {
+ config_base_path = fs::path(config_base_dir);
+ }
+#elif __APPLE__
+ auto config_base_dir = get_env("HOME");
+ config_base_path = fs::path(config_base_dir) / "Library" / "Application Support";
+#endif
+ return config_base_path / "OpenGOAL";
+}
+
+fs::path get_user_settings_dir(GameVersion game_version) {
+ auto game_version_name = game_version_names[game_version];
+ auto config_dir = get_user_config_dir();
+ if (!g_file_path_info.user_config_dir_override.empty()) {
+ config_dir = g_file_path_info.user_config_dir_override / "OpenGOAL";
+ }
+ return config_dir / game_version_name / "settings";
+}
+
+fs::path get_user_memcard_dir(GameVersion game_version) {
+ auto game_version_name = game_version_names[game_version];
+ auto config_dir = get_user_config_dir();
+ if (!g_file_path_info.user_config_dir_override.empty() &&
+ g_file_path_info.use_overridden_config_dir_for_saves) {
+ config_dir = g_file_path_info.user_config_dir_override / "OpenGOAL";
+ }
+ return config_dir / game_version_name / "saves";
+}
+
+fs::path get_user_screenshots_dir(GameVersion game_version) {
+ auto game_version_name = game_version_names[game_version];
+ auto config_dir = get_user_config_dir();
+ if (!g_file_path_info.user_config_dir_override.empty()) {
+ config_dir = g_file_path_info.user_config_dir_override / "OpenGOAL";
+ }
+ return config_dir / game_version_name / "screenshots";
+}
+
+fs::path get_user_misc_dir(GameVersion game_version) {
+ auto game_version_name = game_version_names[game_version];
+ auto config_dir = get_user_config_dir();
+ if (!g_file_path_info.user_config_dir_override.empty()) {
+ config_dir = g_file_path_info.user_config_dir_override / "OpenGOAL";
+ }
+ return config_dir / game_version_name / "misc";
+}
+
+fs::path get_user_features_dir(GameVersion game_version) {
+ auto game_version_name = game_version_names[game_version];
+ auto config_dir = get_user_config_dir();
+ if (!g_file_path_info.user_config_dir_override.empty()) {
+ config_dir = g_file_path_info.user_config_dir_override / "OpenGOAL";
+ }
+ auto path = config_dir / game_version_name / "features";
+ file_util::create_dir_if_needed(path);
+ return path;
+}
+
+fs::path g_iso_data_directory = "";
+
+/*!
+ * Get the path to the current executable.
+ */
+std::string get_current_executable_path() {
+#ifdef _WIN32
+ // NOTE - MAX_PATH is kinda wrong here as you can have a path longer than 260 in windows
+ wchar_t path[MAX_PATH];
+ GetModuleFileNameW(NULL, path, MAX_PATH);
+ std::string file_path = wide_string_to_utf8_string(path);
+ if (file_path.rfind("\\\\?\\", 0) == 0) {
+ return file_path.substr(4);
+ }
+ return file_path;
+#elif __linux
+ char buffer[FILENAME_MAX + 1];
+ auto len = readlink("/proc/self/exe", buffer,
+ FILENAME_MAX); // /proc/self acts like a "virtual folder" containing
+ // information about the current process
+ buffer[len] = '\0';
+ return std::string(buffer);
+#elif __APPLE__
+ char buffer[PATH_MAX];
+ uint32_t bufsize = sizeof(buffer);
+ if (_NSGetExecutablePath(buffer, &bufsize) != 0) {
+ lg::warn("Could not get executable path, trying with _NSGetArgv()[0] instead.");
+ auto argv = *_NSGetArgv();
+ return std::string(argv[0]);
+ }
+ return std::string(buffer);
+#endif
+}
+
+// mod-base-change
+std::optional try_get_project_path_from_path_modbase(const std::string& path) {
+ fs::path current_path = fs::path(path);
+ while (true) {
+ lg::info("Current path in loop - {}", current_path.string());
+ if (fs::exists(current_path / ".github")) {
+ lg::info("Project path found - {}", current_path.string());
+ return current_path.string();
+ }
+ if (!current_path.has_parent_path()){
+ lg::info("No parent folder found");
+ return {};
+ }
+ current_path = current_path.parent_path();
+ }
+}
+
+std::optional try_get_project_path_from_path(const std::string& path) {
+ return try_get_project_path_from_path_modbase(path);
+ std::string::size_type pos =
+ std::string(path).rfind("jak-project"); // Strip file path down to /jak-project/ directory
+ if (pos == std::string::npos) {
+ return {};
+ }
+ return std::string(path).substr(
+ 0, pos + 11); // + 12 to include "/jak-project" in the returned filepath
+}
+
+/*!
+ * See if the current executable is somewhere in jak-project/. If so, return the path to jak-project
+ */
+std::optional try_get_jak_project_path() {
+ return try_get_project_path_from_path(get_current_executable_path());
+}
+
+std::optional try_get_data_dir(bool skip_logs) {
+ fs::path my_path = get_current_executable_path();
+ if (!skip_logs) {
+ lg::debug("Current executable directory - {}", my_path.string());
+ }
+ auto data_dir = my_path.parent_path() / "data";
+ if (fs::exists(data_dir) && fs::is_directory(data_dir)) {
+ return std::make_optional(data_dir);
+ } else {
+ return {};
+ }
+}
+
+bool setup_project_path(std::optional project_path_override, bool skip_logs) {
+ if (g_file_path_info.initialized) {
+ return true;
+ }
+
+ if (project_path_override) {
+ g_file_path_info.path_to_data_folder = fs::absolute(project_path_override.value());
+ g_file_path_info.initialized = true;
+ if (!skip_logs) {
+ lg::debug("Using explicitly set project path: {}",
+ g_file_path_info.path_to_data_folder.string());
+ }
+ return true;
+ }
+
+ auto data_path = try_get_data_dir(skip_logs);
+ if (data_path) {
+ g_file_path_info.path_to_data_folder = *data_path;
+ g_file_path_info.initialized = true;
+ if (!skip_logs) {
+ lg::debug("Using data path: {}", data_path->string());
+ }
+ return true;
+ }
+
+ auto development_repo_path = try_get_jak_project_path();
+ if (development_repo_path) {
+ g_file_path_info.path_to_data_folder = *development_repo_path;
+ g_file_path_info.initialized = true;
+ if (!skip_logs) {
+ lg::debug("Using development repo path: {}", *development_repo_path);
+ }
+ return true;
+ }
+
+ lg::error("Failed to initialize project path.");
+ return false;
+}
+
+void override_user_config_dir(fs::path user_config_dir_override,
+ bool use_overridden_config_dir_for_saves) {
+ g_file_path_info.user_config_dir_override = user_config_dir_override;
+ g_file_path_info.use_overridden_config_dir_for_saves = use_overridden_config_dir_for_saves;
+}
+
+fs::path get_jak_project_dir() {
+ ASSERT(g_file_path_info.initialized);
+ return g_file_path_info.path_to_data_folder;
+}
+
+fs::path get_iso_dir_for_game(GameVersion game_version) {
+ if (!g_iso_data_directory.empty()) {
+ return g_iso_data_directory;
+ }
+ // Find the location based on the game version
+ std::string expected_subdir = "jak1";
+ if (game_version == GameVersion::Jak2) {
+ expected_subdir = "jak2";
+ } else if (game_version == GameVersion::Jak3) {
+ expected_subdir = "jak3";
+ }
+ const auto temp_dir = get_jak_project_dir() / "iso_data" / expected_subdir;
+ if (fs::exists(temp_dir)) {
+ g_iso_data_directory = temp_dir;
+ }
+ return g_iso_data_directory;
+}
+
+void set_iso_data_dir(const fs::path& directory) {
+ g_iso_data_directory = directory;
+}
+
+std::string get_file_path(const std::vector& input) {
+ // TODO - clean this behaviour up, it causes unexpected behaviour when working with files
+ // the project path should be explicitly provided by whatever if needed
+ // TEMP HACK
+ // - if the provided path is absolute, don't add the project path
+ if (input.size() == 1 && fs::path(input.at(0)).is_absolute()) {
+ return input.at(0);
+ }
+
+ auto current_path = file_util::get_jak_project_dir();
+ for (auto& str : input) {
+ current_path /= str;
+ }
+
+ return current_path.string();
+}
+
+bool create_dir_if_needed(const fs::path& path) {
+ if (!fs::is_directory(path)) {
+ fs::create_directories(path);
+ return true;
+ }
+ return false;
+}
+
+// TODO - explodes if the file path is invalid
+bool create_dir_if_needed_for_file(const std::string& path) {
+ return create_dir_if_needed_for_file(fs::path(path));
+}
+
+// TODO - explodes if the file path is invalid
+bool create_dir_if_needed_for_file(const fs::path& path) {
+ return fs::create_directories(path.parent_path());
+}
+
+void write_binary_file(const fs::path& name, const void* data, size_t size) {
+ FILE* fp = file_util::open_file(name.string().c_str(), "wb");
+ if (!fp) {
+ throw std::runtime_error("couldn't open file " + name.string());
+ }
+
+ if (size == 0) {
+ // nothing to write, just 'touch' the file
+ fclose(fp);
+ return;
+ }
+
+ if (fwrite(data, size, 1, fp) != 1) {
+ fclose(fp);
+ throw std::runtime_error("couldn't write file " + name.string());
+ }
+
+ fclose(fp);
+}
+
+void write_binary_file(const std::string& name, const void* data, size_t size) {
+ write_binary_file(fs::path(name), data, size);
+}
+
+void write_rgba_png(const fs::path& name, void* data, int w, int h) {
+ auto flags = 0;
+
+ auto ok = fpng::fpng_encode_image_to_file(name.string().c_str(), data, w, h, 4, flags);
+
+ if (!ok) {
+ throw std::runtime_error("couldn't save png file " + name.string());
+ }
+}
+
+void write_text_file(const std::string& file_name, const std::string& text) {
+ write_text_file(fs::path(file_name), text);
+}
+
+void write_text_file(const fs::path& file_name, const std::string& text) {
+ FILE* fp = file_util::open_file(file_name.string().c_str(), "w");
+ if (!fp) {
+ lg::error("Failed to fopen {}\n", file_name.string());
+ throw std::runtime_error("Failed to open file");
+ }
+ fprintf(fp, "%s\n", text.c_str());
+ fclose(fp);
+}
+std::vector read_binary_file(const std::string& filename) {
+ return read_binary_file(fs::path(filename));
+}
+
+std::vector read_binary_file(const fs::path& path) {
+ // make sure file exists and isn't a directory
+
+ auto status = fs::status(path);
+
+ if (!fs::exists(status)) {
+ throw std::runtime_error(
+ fmt::format("File {} cannot be opened: does not exist.", path.string()));
+ }
+
+ if (status.type() != fs::file_type::regular && status.type() != fs::file_type::symlink) {
+ throw std::runtime_error(
+ fmt::format("File {} cannot be opened: not a regular file or symlink.", path.string()));
+ }
+
+ auto fp = file_util::open_file(path.string().c_str(), "rb");
+ if (!fp)
+ throw std::runtime_error("File " + path.string() +
+ " cannot be opened: " + std::string(strerror(errno)));
+ fseek(fp, 0, SEEK_END);
+ auto len = ftell(fp);
+ if (len == 0) {
+ fclose(fp);
+ return {};
+ }
+ rewind(fp);
+
+ std::vector data;
+ data.resize(len);
+
+ if (fread(data.data(), len, 1, fp) != 1) {
+ fclose(fp);
+ throw std::runtime_error("File " + path.string() + " cannot be read");
+ }
+ fclose(fp);
+
+ return data;
+}
+
+std::string read_text_file(const fs::path& path) {
+ fs::ifstream file(path);
+ if (!file.good()) {
+ throw std::runtime_error("couldn't open " + path.string());
+ }
+ std::stringstream ss;
+ ss << file.rdbuf();
+ return ss.str();
+}
+
+std::string read_text_file(const std::string& path) {
+ return read_text_file(fs::path(path));
+}
+
+bool is_printable_char(char c) {
+ return c >= ' ' && c <= '~';
+}
+
+std::string combine_path(const std::string& parent, const std::string& child) {
+ return parent + "/" + child;
+}
+
+bool file_exists(const std::string& path) {
+ return fs::exists(path);
+}
+
+std::string base_name(const std::string& filename) {
+ size_t pos = 0;
+ ASSERT(!filename.empty());
+ for (size_t i = filename.size() - 1; i-- > 0;) {
+ if (filename.at(i) == '/' || filename.at(i) == '\\') {
+ pos = (i + 1);
+ break;
+ }
+ }
+ return filename.substr(pos);
+}
+
+std::string base_name_no_ext(const std::string& filename) {
+ size_t pos = 0;
+ ASSERT(!filename.empty());
+ for (size_t i = filename.size() - 1; i-- > 0;) {
+ if (filename.at(i) == '/' || filename.at(i) == '\\') {
+ pos = (i + 1);
+ break;
+ }
+ }
+ std::string file_name = filename.substr(pos);
+ return file_name.substr(0, file_name.find_last_of('.'));
+ ;
+}
+
+std::string split_path_at(const fs::path& path, const std::vector& folders) {
+ std::string split_str = "";
+ for (const auto& folder : folders) {
+#ifdef _WIN32
+ split_str += folder + "\\";
+#else
+ split_str += folder + "/";
+#endif
+ }
+ const auto& path_str = path.string();
+ return path_str.substr(path_str.find(split_str) + split_str.length());
+}
+
+std::string convert_to_unix_path_separators(const std::string& path) {
+#ifdef _WIN32
+ std::string copy = path;
+ std::replace(copy.begin(), copy.end(), '\\', '/');
+ return copy;
+#else
+ return path;
+#endif
+}
+
+/*!
+ * Convert an animation name to ISO name.
+ * The animation name is a bunch of dash separated words.
+ * The resulting ISO name has the same first two chars as the animation name, and one char from each
+ * remaining word. Once there are no more words but remaining chars in the ISO name, the ith extra
+ * char is the i+1 th char of the last word. A word ending in a number (or just a number) is turned
+ * into the number. The word "resolution" becomes z. The word "accept" becomes y. The word "reject"
+ * becomes n. Other words become the first char of the word. The result is uppercased and the file
+ * extension is STR Examples (animation name and disc file name, not ISO name):
+ * green-sagecage-outro-beat-boss-enough-cells -> GRSOBBEC.STR
+ * swamp-tetherrock-swamprockexplode-4 -> SWTS4.STR
+ * minershort-resolution-1-orbs -> MIZ1ORBS.STR
+ */
+void ISONameFromAnimationName(char* dst, const char* src) {
+ // The Animation Name is a bunch of words separated by dashes
+
+ // copy first two chars of the first word exactly
+ dst[0] = src[0];
+ dst[1] = src[1];
+ s32 i = 2; // 2 chars added to dst.
+
+ // skip ahead to the first dash (or \0 if there's no dashes)
+ const char* src_ptr = src;
+ while (*src_ptr && *src_ptr != '-') {
+ src_ptr++;
+ }
+
+ // the points to the next dash (or \0 if there's none).
+ const char* next_ptr = src_ptr;
+ if (*src_ptr) {
+ // loop over words (next_ptr points to dash before word, i counts chars in dest)
+ while (src_ptr = next_ptr + 1, i < 8) {
+ // scan next_ptr forward to next dash
+ next_ptr = src_ptr;
+ while (*next_ptr && *next_ptr != '-') {
+ next_ptr++;
+ }
+
+ // there's no next word, so break (the current word will be handled there)
+ if (!*next_ptr)
+ break;
+
+ // add a char for the current word:
+ char char_to_add;
+ if (next_ptr[-1] < '0' || next_ptr[-1] > '9') {
+ // word doesn't end in a number.
+
+ // some special case words map to special letters (likely to avoid animation name conflicts)
+ if (next_ptr - src_ptr == 10 && !memcmp(src_ptr, "resolution", 10)) {
+ // NOTE : jak 2 also allows "res" here but that doesn't work properly.
+ char_to_add = 'z';
+ } else if (next_ptr - src_ptr == 6 && !memcmp(src_ptr, "accept", 6)) {
+ char_to_add = 'y';
+ } else if (next_ptr - src_ptr == 6 && !memcmp(src_ptr, "reject", 6)) {
+ char_to_add = 'n';
+ } else if (next_ptr - src_ptr == 5 && !memcmp(src_ptr, "keira", 5)) {
+ // NOTE : this was added in jak 2. it's safe to use in jak 1 since she was referred to as
+ // "assistant" there
+ char_to_add = 'i';
+ } else {
+ // not a special case, just take the first letter.
+ char_to_add = *src_ptr;
+ }
+ } else {
+ // the current word ends in a number, just use this number (I think usually the whole word
+ // is just a number)
+ char_to_add = next_ptr[-1];
+ }
+
+ dst[i++] = char_to_add;
+ }
+
+ // here we ran out of room in dest, or words in source.
+ // if there's still room in dest and chars in source, just add them
+ while (*src_ptr && (i < 8)) {
+ dst[i] = *src_ptr;
+ src_ptr++;
+ i++;
+ }
+ }
+
+ // pad with spaces (for ISO Name)
+ while (i < 8) {
+ dst[i++] = ' ';
+ }
+
+ // upper case
+ for (i = 0; i < 8; i++) {
+ if (dst[i] >= 'a' && dst[i] <= 'z') {
+ dst[i] -= 0x20;
+ }
+ }
+
+ // append file extension
+ strcpy(dst + 8, "STR");
+}
+
+/*!
+ * Convert file name to "ISO Name"
+ * ISO names are upper case and 12 bytes long.
+ * xxxxxxxxyyy0
+ *
+ * x - uppercase letter of file name, or space
+ * y - uppercase letter of file extension, or space
+ * 0 - null terminator (\0, not the character zero)
+ */
+void MakeISOName(char* dst, const char* src) {
+ int i = 0;
+ const char* src_ptr = src;
+ char* dst_ptr = dst;
+
+ // copy name and upper case
+ while ((i < 8) && (*src_ptr) && (*src_ptr != '.')) {
+ char c = *src_ptr;
+ src_ptr++;
+ if (('`' < c) && (c < '{')) { // lower case
+ c -= 0x20;
+ }
+ *dst_ptr = c;
+ dst_ptr++;
+ i++;
+ }
+
+ // pad out name with spaces
+ while (i < 8) {
+ *dst_ptr = ' ';
+ dst_ptr++;
+ i++;
+ }
+
+ // increment past period
+ if (*src_ptr == '.')
+ src_ptr++;
+
+ // same for extension
+ while (i < 11 && (*src_ptr)) {
+ char c = *src_ptr;
+ src_ptr++;
+ if (('`' < c) && (c < '{')) { // lower case
+ c -= 0x20;
+ }
+ *dst_ptr = c;
+ dst_ptr++;
+ i++;
+ }
+
+ while (i < 11) {
+ *dst_ptr = ' ';
+ dst_ptr++;
+ i++;
+ }
+ *dst_ptr = 0;
+}
+
+void assert_file_exists(const char* path, const char* error_message) {
+ if (!fs::exists(path)) {
+ ASSERT_MSG(false, fmt::format("File {} was not found: {}", path, error_message));
+ }
+}
+
+/*!
+ * Check if the given DGO header (or entire file) is compressed.
+ */
+bool dgo_header_is_compressed(const std::vector& data) {
+ const char compressed_header[] = "oZlB";
+ bool is_compressed = true;
+ for (int i = 0; i < 4; i++) {
+ if (compressed_header[i] != data.at(i)) {
+ is_compressed = false;
+ }
+ }
+ return is_compressed;
+}
+
+/*!
+ * Decompress a DGO. Resulting data will start at the DGO header.
+ */
+std::vector decompress_dgo(const std::vector& data_in) {
+ constexpr int MAX_CHUNK_SIZE = 0x8000;
+ BinaryReader compressed_reader(data_in);
+ // seek past oZlB
+ compressed_reader.ffwd(4);
+ std::size_t decompressed_size = compressed_reader.read();
+ std::vector decompressed_data;
+ decompressed_data.resize(decompressed_size);
+ size_t output_offset = 0;
+ while (true) {
+ // seek past alignment bytes and read the next chunk size
+ uint32_t chunk_size = 0;
+ while (!chunk_size) {
+ chunk_size = compressed_reader.read();
+ }
+
+ if (chunk_size < MAX_CHUNK_SIZE) {
+ std::size_t bytes_written = 0;
+ lzokay::EResult ok = lzokay::decompress(
+ compressed_reader.here(), chunk_size, decompressed_data.data() + output_offset,
+ decompressed_data.size() - output_offset, bytes_written);
+ ASSERT(ok == lzokay::EResult::Success);
+ compressed_reader.ffwd(chunk_size);
+ output_offset += bytes_written;
+ } else {
+ // nope - sometimes chunk_size is bigger than MAX, but we should still use max.
+ // ASSERT(chunk_size == MAX_CHUNK_SIZE);
+ memcpy(decompressed_data.data() + output_offset, compressed_reader.here(), MAX_CHUNK_SIZE);
+ compressed_reader.ffwd(MAX_CHUNK_SIZE);
+ output_offset += MAX_CHUNK_SIZE;
+ }
+
+ if (output_offset >= decompressed_size)
+ break;
+ while (compressed_reader.get_seek() % 4) {
+ compressed_reader.ffwd(1);
+ }
+ }
+
+ return decompressed_data;
+}
+
+FILE* open_file(const fs::path& path, const std::string& mode) {
+#ifdef _WIN32
+ return _wfopen(path.wstring().c_str(), std::wstring(mode.begin(), mode.end()).c_str());
+#else
+ return fopen(path.string().c_str(), mode.c_str());
+#endif
+}
+
+std::vector find_files_in_dir(const fs::path& dir, const std::regex& pattern) {
+ std::vector files = {};
+ for (auto& p : fs::directory_iterator(dir)) {
+ if (p.is_regular_file()) {
+ if (std::regex_match(p.path().filename().string(), pattern)) {
+ files.push_back(p.path());
+ }
+ }
+ }
+ return files;
+}
+
+std::vector find_files_recursively(const fs::path& base_dir, const std::regex& pattern) {
+ std::vector files = {};
+ if (!fs::exists(base_dir)) {
+ return files;
+ }
+ for (auto& p : fs::recursive_directory_iterator(base_dir)) {
+ if (p.is_regular_file()) {
+ if (std::regex_match(p.path().filename().string(), pattern)) {
+ files.push_back(p.path());
+ }
+ }
+ }
+ return files;
+}
+
+std::vector find_directories_in_dir(const fs::path& base_dir) {
+ std::vector dirs = {};
+ for (auto& p : fs::recursive_directory_iterator(base_dir)) {
+ if (p.is_directory()) {
+ dirs.push_back(p.path());
+ }
+ }
+ return dirs;
+}
+
+std::vector sort_filepaths(const std::vector& paths, const bool aescending) {
+ std::vector paths_as_strings = {};
+ for (const auto& path : paths) {
+ paths_as_strings.push_back(path.string());
+ }
+ std::sort(paths_as_strings.begin(), paths_as_strings.end(),
+ [aescending](const std::string& a, const std::string& b) {
+ if (aescending) {
+ return a < b;
+ } else {
+ return a > b;
+ }
+ });
+ std::vector sorted_paths = {};
+ for (const auto& path : paths_as_strings) {
+ sorted_paths.push_back(fs::path(path));
+ }
+ return sorted_paths;
+}
+
+void copy_file(const fs::path& src, const fs::path& dst) {
+ // Check that the src path exists
+ if (!fs::exists(src)) {
+ throw std::runtime_error(fmt::format("Cannot copy '{}', path does not exist", src.string()));
+ }
+ // Ensure the directory can be copied into
+ if (!fs::exists(dst.parent_path()) && !create_dir_if_needed_for_file(dst)) {
+ throw std::runtime_error(fmt::format(
+ "Cannot copy '{}', couldn't make directory to copy into '{}'", src.string(), dst.string()));
+ }
+ fs::copy_file(src, dst, fs::copy_options::overwrite_existing);
+}
+
+std::string make_screenshot_filepath(const GameVersion game_version, const std::string& name) {
+ std::string file_name;
+ if (name.empty()) {
+ file_name = fmt::format("{}.png", str_util::current_local_timestamp_no_colons());
+ } else {
+ file_name = fmt::format("{}.png", name);
+ }
+ const auto file_path = get_user_screenshots_dir(game_version) / file_name;
+ file_util::create_dir_if_needed_for_file(file_path);
+ return file_path.string();
+}
+
+std::string get_majority_file_line_endings(const std::string& file_contents) {
+ size_t lf_count = 0;
+ size_t crlf_count = 0;
+
+ for (size_t i = 0; i < file_contents.size(); ++i) {
+ if (file_contents[i] == '\n') {
+ if (i > 0 && file_contents[i - 1] == '\r') {
+ crlf_count++;
+ } else {
+ lf_count++;
+ }
+ }
+ }
+
+ if (crlf_count > lf_count) {
+ return "\r\n";
+ }
+ return "\n";
+}
+
+std::pair get_majority_file_line_endings_and_count(
+ const std::string& file_contents) {
+ size_t lf_count = 0;
+ size_t crlf_count = 0;
+
+ for (size_t i = 0; i < file_contents.size(); ++i) {
+ if (file_contents[i] == '\n') {
+ if (i > 0 && file_contents[i - 1] == '\r') {
+ crlf_count++;
+ } else {
+ lf_count++;
+ }
+ }
+ }
+
+ if (crlf_count > lf_count) {
+ return {lf_count + crlf_count, "\r\n"};
+ }
+ return {lf_count + crlf_count, "\n"};
+}
+
+bool is_dir_in_dir(const fs::path& parent, const fs::path& child) {
+ // Check if the parent path is a prefix of the child path
+ return child.has_parent_path() && child.parent_path().lexically_relative(parent) == fs::path(".");
+}
+
+} // namespace file_util
diff --git a/common/util/FileUtil.h b/common/util/FileUtil.h
index 16875c77bf..72d5daf2bd 100644
--- a/common/util/FileUtil.h
+++ b/common/util/FileUtil.h
@@ -1,83 +1,83 @@
-#pragma once
-
-/*!
- * @file FileUtil.h
- * Utility functions for reading and writing files.
- */
-
-#ifdef _WIN32
-#define NOMINMAX
-#define WIN32_LEAN_AND_MEAN
-#endif
-
-#include "third-party/filesystem.hpp"
-
-#ifdef _WIN32
-#undef FALSE
-#endif
-
-#include
-#include
-#include
-#include
-
-#include "common/common_types.h"
-#include "common/versions/versions.h"
-
-namespace fs = ghc::filesystem;
-
-namespace file_util {
-fs::path get_user_home_dir();
-fs::path get_user_config_dir();
-fs::path get_user_settings_dir(GameVersion game_version);
-fs::path get_user_memcard_dir(GameVersion game_version);
-fs::path get_user_screenshots_dir(GameVersion game_version);
-fs::path get_user_misc_dir(GameVersion game_version);
-fs::path get_user_features_dir(GameVersion game_version);
-fs::path get_jak_project_dir();
-fs::path get_iso_dir_for_game(GameVersion game_version);
-void set_iso_data_dir(const fs::path& directory);
-
-bool create_dir_if_needed(const fs::path& path);
-bool create_dir_if_needed_for_file(const std::string& path);
-bool create_dir_if_needed_for_file(const fs::path& path);
-std::string get_current_executable_path();
-std::optional try_get_project_path_from_path(const std::string& path);
-bool setup_project_path(std::optional project_path_override, bool skip_logs = false);
-void override_user_config_dir(fs::path user_config_dir_override,
- bool use_overridden_config_dir_for_saves);
-std::string get_file_path(const std::vector& path);
-void write_binary_file(const std::string& name, const void* data, size_t size);
-void write_binary_file(const fs::path& name, const void* data, size_t size);
-void write_rgba_png(const fs::path& name, void* data, int w, int h);
-void write_text_file(const std::string& file_name, const std::string& text);
-void write_text_file(const fs::path& file_name, const std::string& text);
-std::vector read_binary_file(const std::string& filename);
-std::vector read_binary_file(const fs::path& filename);
-std::string read_text_file(const std::string& path);
-std::string read_text_file(const fs::path& path);
-bool is_printable_char(char c);
-std::string combine_path(const std::string& parent, const std::string& child);
-bool file_exists(const std::string& path);
-std::string base_name(const std::string& filename);
-std::string base_name_no_ext(const std::string& filename);
-std::string split_path_at(const fs::path& path, const std::vector& folders);
-std::string convert_to_unix_path_separators(const std::string& path);
-void MakeISOName(char* dst, const char* src);
-void ISONameFromAnimationName(char* dst, const char* src);
-void assert_file_exists(const char* path, const char* error_message);
-bool dgo_header_is_compressed(const std::vector& data);
-std::vector decompress_dgo(const std::vector& data_in);
-FILE* open_file(const fs::path& path, const std::string& mode);
-std::vector find_files_in_dir(const fs::path& dir, const std::regex& pattern);
-std::vector find_files_recursively(const fs::path& base_dir, const std::regex& pattern);
-std::vector find_directories_in_dir(const fs::path& base_dir);
-std::vector sort_filepaths(const std::vector& paths, const bool aescending);
-/// Will overwrite the destination if it exists
-void copy_file(const fs::path& src, const fs::path& dst);
-std::string make_screenshot_filepath(const GameVersion game_version, const std::string& name = "");
-std::string get_majority_file_line_endings(const std::string& file_contents);
-std::pair get_majority_file_line_endings_and_count(
- const std::string& file_contents);
-bool is_dir_in_dir(const fs::path& parent, const fs::path& child);
-} // namespace file_util
+#pragma once
+
+/*!
+ * @file FileUtil.h
+ * Utility functions for reading and writing files.
+ */
+
+#ifdef _WIN32
+#define NOMINMAX
+#define WIN32_LEAN_AND_MEAN
+#endif
+
+#include "third-party/filesystem.hpp"
+
+#ifdef _WIN32
+#undef FALSE
+#endif
+
+#include
+#include
+#include
+#include
+
+#include "common/common_types.h"
+#include "common/versions/versions.h"
+
+namespace fs = ghc::filesystem;
+
+namespace file_util {
+fs::path get_user_home_dir();
+fs::path get_user_config_dir();
+fs::path get_user_settings_dir(GameVersion game_version);
+fs::path get_user_memcard_dir(GameVersion game_version);
+fs::path get_user_screenshots_dir(GameVersion game_version);
+fs::path get_user_misc_dir(GameVersion game_version);
+fs::path get_user_features_dir(GameVersion game_version);
+fs::path get_jak_project_dir();
+fs::path get_iso_dir_for_game(GameVersion game_version);
+void set_iso_data_dir(const fs::path& directory);
+
+bool create_dir_if_needed(const fs::path& path);
+bool create_dir_if_needed_for_file(const std::string& path);
+bool create_dir_if_needed_for_file(const fs::path& path);
+std::string get_current_executable_path();
+std::optional try_get_project_path_from_path(const std::string& path);
+bool setup_project_path(std::optional project_path_override, bool skip_logs = false);
+void override_user_config_dir(fs::path user_config_dir_override,
+ bool use_overridden_config_dir_for_saves);
+std::string get_file_path(const std::vector& path);
+void write_binary_file(const std::string& name, const void* data, size_t size);
+void write_binary_file(const fs::path& name, const void* data, size_t size);
+void write_rgba_png(const fs::path& name, void* data, int w, int h);
+void write_text_file(const std::string& file_name, const std::string& text);
+void write_text_file(const fs::path& file_name, const std::string& text);
+std::vector read_binary_file(const std::string& filename);
+std::vector read_binary_file(const fs::path& filename);
+std::string read_text_file(const std::string& path);
+std::string read_text_file(const fs::path& path);
+bool is_printable_char(char c);
+std::string combine_path(const std::string& parent, const std::string& child);
+bool file_exists(const std::string& path);
+std::string base_name(const std::string& filename);
+std::string base_name_no_ext(const std::string& filename);
+std::string split_path_at(const fs::path& path, const std::vector& folders);
+std::string convert_to_unix_path_separators(const std::string& path);
+void MakeISOName(char* dst, const char* src);
+void ISONameFromAnimationName(char* dst, const char* src);
+void assert_file_exists(const char* path, const char* error_message);
+bool dgo_header_is_compressed(const std::vector& data);
+std::vector decompress_dgo(const std::vector& data_in);
+FILE* open_file(const fs::path& path, const std::string& mode);
+std::vector find_files_in_dir(const fs::path& dir, const std::regex& pattern);
+std::vector find_files_recursively(const fs::path& base_dir, const std::regex& pattern);
+std::vector find_directories_in_dir(const fs::path& base_dir);
+std::vector sort_filepaths(const std::vector& paths, const bool aescending);
+/// Will overwrite the destination if it exists
+void copy_file(const fs::path& src, const fs::path& dst);
+std::string make_screenshot_filepath(const GameVersion game_version, const std::string& name = "");
+std::string get_majority_file_line_endings(const std::string& file_contents);
+std::pair get_majority_file_line_endings_and_count(
+ const std::string& file_contents);
+bool is_dir_in_dir(const fs::path& parent, const fs::path& child);
+} // namespace file_util
diff --git a/custom_assets/blender positions to json.py b/custom_assets/blender positions to json.py
new file mode 100644
index 0000000000..7eb210cc86
--- /dev/null
+++ b/custom_assets/blender positions to json.py
@@ -0,0 +1,214 @@
+import bpy
+import json
+import os
+
+
+#Cells
+# Get the collection
+collection = bpy.data.collections.get("Cell Collection")
+print(r"// Start automatic actors from blender")
+print()
+# Check if the collection exists
+if collection is None:
+ print(r"//Cell Collection not found")
+ print()
+else:
+ # Create a list to store the positions of all objects in the collection
+ positions = []
+
+ # Get the name of the blend file without the extension
+ blend_file_name = bpy.path.display_name_from_filepath(bpy.data.filepath).replace(" ", "-")
+ etype = "fuel-cell"
+
+ # Counter for generating incrementing numbers
+ count = 1
+
+ # Loop through all objects in the collection
+ for obj in collection.objects:
+ # Get the object's position and bounding sphere
+ pos = obj.location
+ bsphere = obj.bound_box
+
+ # Create a dictionary to store the object's data
+ data = {
+ "trans": [pos.x, pos.z, -1 * pos.y],
+ "etype": etype,
+ "game_task": 2,
+ "quat": [0, 0, 0, 1],
+ "bsphere": [0.0, 0.0, 0.0, 0.0],
+ "lump": {
+ "name": f"{blend_file_name}-{etype}-{count}",
+ "eco-info": ["int32", 6, 2],
+ #"movie-pos": [pos.x, pos.z, -1 * pos.y],
+ }
+ }
+
+ # Increment the counter
+ count += 1
+
+ # Add the object's data to the list of positions
+ positions.append(data)
+
+ # Output the positions as JSON
+ print(r"//Cells below")
+ print(json.dumps(positions))
+ print()
+
+
+#Orbs
+# Get the collection
+collection = bpy.data.collections.get("Orb Collection")
+
+# Check if the collection exists
+if collection is None:
+ print(r"//Orb Collection not found")
+ print()
+else:
+ # Create a list to store the positions of all objects in the collection
+ positions = []
+
+ # Get the name of the blend file without the extension
+ blend_file_name = bpy.path.display_name_from_filepath(bpy.data.filepath).replace(" ", "-")
+ etype = "money"
+
+ # Counter for generating incrementing numbers
+ count = 1
+
+ # Loop through all objects in the collection
+ for obj in collection.objects:
+ # Get the object's position and bounding sphere
+ pos = obj.location
+ bsphere = obj.bound_box
+
+ # Create a dictionary to store the object's data
+ data = {
+ "trans": [pos.x, pos.z, -1 * pos.y],
+ "etype": etype,
+ "game_task": 0,
+ "quat": [0, 0, 0, 1],
+ "bsphere": [0.0, 0.0, 0.0, 0.0],
+ "lump": {
+ "name": f"{blend_file_name}-{etype}-{count}",
+ }
+ }
+
+ # Increment the counter
+ count += 1
+
+ # Add the object's data to the list of positions
+ positions.append(data)
+
+ # Output the positions as JSON
+ print(r"//Orbs below")
+ print(json.dumps(positions))
+ print()
+
+#Crates
+# Get the collection
+collection = bpy.data.collections.get("Crate Collection")
+
+# Check if the collection exists
+if collection is None:
+ print(r"//Crate Collection not found")
+ print()
+else:
+ # Create a list to store the positions of all objects in the collection
+ positions = []
+
+ # Get the name of the blend file without the extension
+ blend_file_name = bpy.path.display_name_from_filepath(bpy.data.filepath).replace(" ", "-")
+ etype = "crate"
+
+ # Counter for generating incrementing numbers
+ count = 1
+
+ # Loop through all objects in the collection
+ for obj in collection.objects:
+ # Get the object's position and bounding sphere
+ pos = obj.location
+ bsphere = obj.bound_box
+
+ # Create a dictionary to store the object's data
+ data = {
+ "trans": [pos.x, pos.z, -1 * pos.y],
+ "etype": etype,
+ "game_task": 0,
+ "quat": [0, 0, 0, 1],
+ "bsphere": [0.0, 0.0, 0.0, 0.0],
+ "lump": {
+ "name": f"{blend_file_name}-{etype}-{count}",
+ }
+ }
+
+ # Increment the counter
+ count += 1
+
+ # Add the object's data to the list of positions
+ positions.append(data)
+
+ # Output the positions as JSON
+ print(r"//Crates below")
+ print(json.dumps(positions))
+ print()
+
+print(r"//End automatic actors from blender")
+
+
+
+# Define the path to the glb models
+glb_model_path = r"C:\Users\NinjaPC\AppData\Roaming\Blender Foundation\Blender\3.2\scripts\addons\OpenMaya\actorsa"
+
+# Define the mapping of etype to glb model filenames
+model_mapping = {
+ "fuel-cell": "fuel.glb",
+ "money": "money.glb",
+ "crate": "crate-wood.glb",
+}
+# Function to replace the model of an object while preserving its properties
+def replace_object_model(obj, glb_filename, new_collection):
+ # Store object properties
+ obj_name = obj.name
+ obj_location = obj.location.copy()
+
+ # Remove the old object
+ bpy.data.objects.remove(obj, do_unlink=True)
+
+ # Create a new object with the glb model
+ bpy.ops.import_scene.gltf(filepath=os.path.join(glb_model_path, glb_filename))
+ new_obj = bpy.context.selected_objects[0]
+
+ # Set properties of the new object based on the old object
+ new_obj.name = obj_name
+ new_obj.location = obj_location
+
+ # Link the new object to the specified collection
+ # Remove the new object link to the main scene too
+ new_collection.objects.link(new_obj)
+ bpy.context.scene.collection.objects.unlink(new_obj)
+ return new_obj
+
+# Cells
+cell_collection = bpy.data.collections.get("Cell Collection")
+if cell_collection:
+ objects_to_replace = list(cell_collection.objects)
+ for obj in objects_to_replace:
+ glb_filename = model_mapping["fuel-cell"]
+ replace_object_model(obj, glb_filename, cell_collection)
+
+# Orbs
+orb_collection = bpy.data.collections.get("Orb Collection")
+if orb_collection:
+ for obj in list(orb_collection.objects): # Iterate through a copy of the objects
+ glb_filename = model_mapping["money"]
+ replace_object_model(obj, glb_filename, orb_collection)
+
+
+# Crates
+crate_collection = bpy.data.collections.get("Crate Collection")
+if crate_collection:
+ for obj in crate_collection.objects:
+ etype = obj.get("etype")
+ if etype in model_mapping:
+ glb_filename = model_mapping[etype]
+ replace_object_model(obj, glb_filename, crate_collection)
+
diff --git a/custom_assets/jak1/levels/test-zone/test-zone.jsonc b/custom_assets/jak1/levels/test-zone/test-zone.jsonc
index a5b22cc89b..e022274d94 100644
--- a/custom_assets/jak1/levels/test-zone/test-zone.jsonc
+++ b/custom_assets/jak1/levels/test-zone/test-zone.jsonc
@@ -56,11 +56,12 @@
// "symbol-list": ["symbol", "sym-1", "sym-2"]
// The base actor id for your custom level. If you have multiple levels, this should be unique!
- "base_id": 100,
+ "base_id": 10000,
// All art groups you want to use in your custom level. Will add their models and corresponding textures to the FR3 file.
// Note: You will still have to add them to your level's .gd file.
// "art_groups": ["plat-ag"],
+ "art_groups": ["babak-ag", "plat-ag", "plat-eco-ag"],
// If you have any custom models in the "custom_assets/jak1/models/custom_levels" folder that you want to use in your level, add them to this list.
// Note: Like with art groups, these should also be added to your level's .gd file.
@@ -133,6 +134,30 @@
"name": "test-eco"
}
},
+ {
+ "trans": [-7.3159, 1.0468, 20.3499],
+ "etype": "babak",
+ "game_task": 0,
+ "quat" : [0, 0, 0, 1],
+ "bsphere": [-7.3159, 1.0468, 20.3499, 10],
+ "lump": {
+ "name": "test-babak",
+ "vis-dist": ["meters", 200.0],
+ "cam-notice-dist": ["meters", 30.0],
+ "idle-distance": ["meters", 50.0],
+ "cam-vert": ["meters", 8.0],
+ "cam-horz": ["meters", 20.0],
+ "path": ["vector3m", // I am pretty sure game will crash if there is no path for an enemy. So just add one. You can use "path-actor" if you want to use another actor's path.
+ [-7.3159, 1.0468, 20.3499],
+ [-2.3607, 1.0468, 12.8255],
+ [-9.7266, 1.0468, 14.4445],
+ [-7.3159, 1.0468, 20.3499]
+ ],
+ "nav-mesh-sphere": ["vector4m", // You can define spheres that will be added to the navmesh of the enemy. Enemies of the navmesh will avoid these spheres - like they avoid each other.
+ [-5.3159, 1.0468, 16.3499, 1.0]
+ ]
+ }
+ },
{
"trans": [-5.41, 3.5, 28.42], // translation
"etype": "test-actor", // actor type
@@ -142,16 +167,127 @@
"lump": {
"name": "test-actor"
}
+ },
+
+ {
+ "trans": [0.0, 50.0, 10.0],
+ "etype": "linear-plat",
+ "game_task": 0,
+ "quat": [0, 0, 0, 1],
+ "bsphere": [0.0, 50.0, 10.0, 10],
+ "lump": {
+ "name": "linear-plat-0",
+ "path": ["vector4m",
+ [0.0, 50.0, 10.0, 1.0],
+ [0.0, 50.0, 10.0, 1.0],
+ [0.0, 60.0, 10.0, 1.0],
+ [0.0, 50.0, 10.0, 1.0],
+ [0.0, 50.0, 10.0, 1.0],
+ [0.0, 60.0, 10.0, 1.0],
+ [0.0, 50.0, 10.0, 1.0]
+ ],
+ "timings": ["float",
+ 0.0, // offset before starting
+ 2.0, // down to up (out)
+ 2.0, // up to down (out)
+ 6.0, // stay down
+ 2.0, // down to up (back)
+ 2.0, // up to down (back)
+ 0.0 // immediately start back up
+ ],
+ "sync": ["float", 14, 0.0]
+ }
+ },
+
+ {
+ "trans": [0.0, 50.0, 17.0],
+ "etype": "linear-plat",
+ "game_task": 0,
+ "quat": [0, 0, 0, 1],
+ "bsphere": [0.0, 50.0, 17.0, 10],
+ "lump": {
+ "name": "linear-plat-1",
+ "path": ["vector4m",
+ [0.0, 50.0, 17.0, 1.0],
+ [0.0, 50.0, 17.0, 1.0],
+ [0.0, 60.0, 17.0, 1.0],
+ [0.0, 50.0, 17.0, 1.0],
+ [0.0, 50.0, 17.0, 1.0],
+ [0.0, 60.0, 17.0, 1.0],
+ [0.0, 50.0, 17.0, 1.0]
+ ],
+ "timings": ["float",
+ 1.0, // offset before starting
+ 2.0, // down to up (out)
+ 2.0, // up to down (out)
+ 4.0, // stay down
+ 2.0, // down to up (back)
+ 2.0, // up to down (back)
+ 1.0 // immediately start back up
+ ],
+ "sync": ["float", 14, 0.0]
+ }
+ },
+
+ {
+ "trans": [0.0, 50.0, 24.0],
+ "etype": "linear-plat",
+ "game_task": 0,
+ "quat": [0, 0, 0, 1],
+ "bsphere": [0.0, 50.0, 24.0, 10],
+ "lump": {
+ "name": "linear-plat-2",
+ "path": ["vector4m",
+ [0.0, 50.0, 24.0, 1.0],
+ [0.0, 50.0, 24.0, 1.0],
+ [0.0, 60.0, 24.0, 1.0],
+ [0.0, 50.0, 24.0, 1.0],
+ [0.0, 50.0, 24.0, 1.0],
+ [0.0, 60.0, 24.0, 1.0],
+ [0.0, 50.0, 24.0, 1.0]
+ ],
+ "timings": ["float",
+ 2.0, // offset before starting
+ 2.0, // down to up (out)
+ 2.0, // up to down (out)
+ 2.0, // stay down
+ 2.0, // down to up (back)
+ 2.0, // up to down (back)
+ 2.0 // immediately start back up
+ ],
+ "sync": ["float", 14, 0.0]
+ }
+ },
+
+ {
+ "trans": [0.0, 50.0, 31.0],
+ "etype": "linear-plat",
+ "game_task": 0,
+ "quat": [0, 0, 0, 1],
+ "bsphere": [0.0, 50.0, 31.0, 10],
+ "lump": {
+ "name": "linear-plat-3",
+ "path": ["vector4m",
+ [0.0, 50.0, 31.0, 1.0],
+ [0.0, 50.0, 31.0, 1.0],
+ [0.0, 60.0, 31.0, 1.0],
+ [0.0, 50.0, 31.0, 1.0],
+ [0.0, 50.0, 31.0, 1.0],
+ [0.0, 60.0, 31.0, 1.0],
+ [0.0, 50.0, 31.0, 1.0]
+ ],
+ "timings": ["float",
+ 3.0, // offset before starting
+ 2.0, // down to up (out)
+ 2.0, // up to down (out)
+ 0.0, // stay down
+ 2.0, // down to up (back)
+ 2.0, // up to down (back)
+ 0.0 // immediately start back up
+ ],
+ "sync": ["float", 14, 0.0]
+ }
}
- // {
- // "trans": [-7.41, 3.5, 28.42], // translation
- // "etype": "plat", // actor type
- // "game_task": "(game-task none)", // associated game task (for powercells, etc)
- // "quat": [0, 0, 0, 1], // quaternion
- // "bsphere": [-7.41, 3.5, 28.42, 10], // bounding sphere
- // "lump": {
- // "name": "test-plat"
- // }
- // }
+
]
}
\ No newline at end of file
diff --git a/custom_assets/jak1/levels/test-zone/testzone.gd b/custom_assets/jak1/levels/test-zone/testzone.gd
index 8889f9bbc5..8ce4b2a400 100644
--- a/custom_assets/jak1/levels/test-zone/testzone.gd
+++ b/custom_assets/jak1/levels/test-zone/testzone.gd
@@ -5,7 +5,10 @@
("TSZ.DGO"
("static-screen.o"
"test-zone-obs.o"
+ "linear-plat.o"
"plat-ag.go"
+ "plat-eco-ag.go"
"test-actor-ag.go"
+ "babak-ag.go"
"test-zone.go"
))
\ No newline at end of file
diff --git a/decompiler/ObjectFile/ObjectFileDB_IR2.cpp b/decompiler/ObjectFile/ObjectFileDB_IR2.cpp
index 36e76e1279..c1eb3e8cbe 100644
--- a/decompiler/ObjectFile/ObjectFileDB_IR2.cpp
+++ b/decompiler/ObjectFile/ObjectFileDB_IR2.cpp
@@ -12,6 +12,7 @@
#include "common/util/FileUtil.h"
#include "common/util/Timer.h"
#include "common/util/string_util.h"
+#include
#include "decompiler/IR2/Form.h"
#include "decompiler/analysis/analyze_inspect_method.h"
diff --git a/decompiler/config.cpp b/decompiler/config.cpp
index a47df7c4b6..d3fea94537 100644
--- a/decompiler/config.cpp
+++ b/decompiler/config.cpp
@@ -329,6 +329,10 @@ Config make_config_via_json(nlohmann::json& json) {
inputs_json.at("animated_textures").get>();
}
+ if (json.contains("common_art_groups")) {
+ config.common_art_groups = json.at("common_art_groups").get>();
+ }
+
if (inputs_json.contains("common_tpages")) {
config.common_tpages = inputs_json.at("common_tpages").get>();
}
diff --git a/decompiler/config.h b/decompiler/config.h
index 7ac7793a25..12c0bae09e 100644
--- a/decompiler/config.h
+++ b/decompiler/config.h
@@ -168,6 +168,7 @@ struct Config {
std::unordered_map bad_format_strings;
std::unordered_set animated_textures;
+ std::unordered_set common_art_groups;
std::unordered_set common_tpages;
std::vector levels_to_extract;
diff --git a/decompiler/config/jak1/all-types.gc b/decompiler/config/jak1/all-types.gc
index fa6d3e1183..3b1c004f28 100644
--- a/decompiler/config/jak1/all-types.gc
+++ b/decompiler/config/jak1/all-types.gc
@@ -1669,6 +1669,7 @@
(speedrun-hub2-100 #x1522)
(speedrun-hub3-100 #x1523)
(speedrun-all-cutscenes #x1524)
+
;; input options
(input-options #x1600)
(input-opts-select-controller #x1601)
@@ -15811,6 +15812,7 @@
(quit 34)
;; extra screens for pc port
+
;; input options
(input-options)
(select-controller)
diff --git a/decompiler/config/jak1/jak1_config.jsonc b/decompiler/config/jak1/jak1_config.jsonc
index ed1be81595..b0f5b74655 100644
--- a/decompiler/config/jak1/jak1_config.jsonc
+++ b/decompiler/config/jak1/jak1_config.jsonc
@@ -125,6 +125,12 @@
// whether or not to dump out streamed audio files to decompiler_out//audio
"rip_streamed_audio": false,
+ // art groups that should always be possible to access, e.g. "common_art_groups": ["eichar-racer+0-ag", "racer-ag", "ef-plane-ag"]
+ "common_art_groups": [],
+
+ // tpages that should always be possible to access, e.g. "common_tpages": [1119],
+ "common_tpages": [],
+
////////////////////////////
// PATCHING OPTIONS
////////////////////////////
diff --git a/decompiler/extractor/extractor_util.cpp b/decompiler/extractor/extractor_util.cpp
index d3f58eb54d..476c4a243b 100644
--- a/decompiler/extractor/extractor_util.cpp
+++ b/decompiler/extractor/extractor_util.cpp
@@ -118,6 +118,16 @@ extractor_iso_database() {
"ko", // decompiler config
"jak2",
{}}}}},
+ // Jak 3 NTSC-U
+ {"SCUS-97330", // serial from ELF name
+ {{4975852519304227343, // hash of ELF
+ {"Jak 3", // canonical name
+ GAME_TERRITORY_SCEA,
+ 749, // number of files
+ {1197801364027358161}, // iso hash
+ "ntsc_v1", // decompiler config
+ "jak3",
+ {}}}}},
};
return database;
}
diff --git a/decompiler/extractor/main.cpp b/decompiler/extractor/main.cpp
index 241a6cc0f5..1635e16caa 100644
--- a/decompiler/extractor/main.cpp
+++ b/decompiler/extractor/main.cpp
@@ -14,7 +14,8 @@
// used for - decompiler_out/ and iso_data/
const std::unordered_map data_subfolders = {{"jak1", "jak1"},
- {"jak2", "jak2"}};
+ {"jak2", "jak2"},
+ {"jak3", "jak3"}};
IsoFile extract_files(fs::path input_file_path, fs::path extracted_iso_path) {
lg::info(
diff --git a/decompiler/level_extractor/extract_level.cpp b/decompiler/level_extractor/extract_level.cpp
index e77ef5d624..7e541dd98f 100644
--- a/decompiler/level_extractor/extract_level.cpp
+++ b/decompiler/level_extractor/extract_level.cpp
@@ -257,7 +257,8 @@ void extract_common(const ObjectFileDB& db,
const TextureDB& tex_db,
const std::string& dgo_name,
const fs::path& output_folder,
- const Config& config) {
+ const Config& config,
+ const std::vector& all_dgo_names) {
if (db.obj_files_by_dgo.count(dgo_name) == 0) {
lg::warn("Skipping common extract for {} because the DGO was not part of the input", dgo_name);
return;
@@ -278,6 +279,35 @@ void extract_common(const ObjectFileDB& db,
add_all_textures_from_level(tfrag_level, "ARTSPOOL", tex_db);
extract_art_groups_from_level(db, tex_db, {}, "ARTSPOOL", tfrag_level, art_group_data);
+ // copy in any art groups that were requested to be common
+ if (config.common_art_groups.size() > 0) {
+ std::unordered_set art_groups_made_common;
+ for (const std::string& lvl_dgo_name : all_dgo_names) {
+ // exit early if we've found everything
+ if (config.common_art_groups.size() == art_groups_made_common.size()) {
+ lg::info("Found all requested art groups to be made common!");
+ break;
+ }
+
+ lg::info("Looking for common art groups in {}", lvl_dgo_name);
+ auto tex_remap = extract_tex_remap(db, lvl_dgo_name);
+ if (db.obj_files_by_dgo.count(lvl_dgo_name)) {
+ const auto& files = db.obj_files_by_dgo.at(lvl_dgo_name);
+ for (const auto& file : files) {
+ if (!art_groups_made_common.contains(file.name) && config.common_art_groups.contains(file.name)) {
+ lg::info("Art group {} was requested to be made common, we found it in {}!", file.name,
+ lvl_dgo_name);
+ const auto& ag_file = db.lookup_record(file);
+ extract_merc(ag_file, tex_db, db.dts, tex_remap, tfrag_level, false, db.version());
+ extract_joint_group(ag_file, db.dts, db.version(), art_group_data);
+ // track found art groups so we don't borther re-processing in a later level
+ art_groups_made_common.insert(file.name);
+ }
+ }
+ }
+ }
+ }
+
std::set textures_we_have;
// put _all_ index textures in common.
@@ -378,7 +408,7 @@ void extract_all_levels(const ObjectFileDB& db,
const std::string& common_name,
const Config& config,
const fs::path& output_path) {
- extract_common(db, tex_db, common_name, output_path, config);
+ extract_common(db, tex_db, common_name, output_path, config, dgo_names);
auto entities_dir = file_util::get_jak_project_dir() / "decompiler_out" /
game_version_names[config.game_version] / "entities";
file_util::create_dir_if_needed(entities_dir);
diff --git a/decompiler/level_extractor/merc_replacement.cpp b/decompiler/level_extractor/merc_replacement.cpp
index 1cc4205faa..07e2e2d4e9 100644
--- a/decompiler/level_extractor/merc_replacement.cpp
+++ b/decompiler/level_extractor/merc_replacement.cpp
@@ -131,9 +131,9 @@ void merc_convert_replacement(MercSwapData& out,
x.pos[0] = y.x;
x.pos[1] = y.y;
x.pos[2] = y.z;
- x.normal[0] = copy_from.normal[0];
- x.normal[1] = copy_from.normal[1];
- x.normal[2] = copy_from.normal[2];
+ x.normal[0] = in.normals.at(i).x();
+ x.normal[1] = in.normals.at(i).y();
+ x.normal[2] = in.normals.at(i).z();
x.weights[0] = copy_from.weights[0];
x.weights[1] = copy_from.weights[1];
x.weights[2] = copy_from.weights[2];
diff --git a/decompiler/scripts/create_dgo_name_list.py b/decompiler/scripts/create_dgo_name_list.py
old mode 100755
new mode 100644
diff --git a/game/assets/jak1/game_subtitle.gp b/game/assets/jak1/game_subtitle.gp
index 28283be418..bfd92ae96c 100644
--- a/game/assets/jak1/game_subtitle.gp
+++ b/game/assets/jak1/game_subtitle.gp
@@ -133,6 +133,7 @@
:meta "game/assets/jak1/subtitle/subtitle_meta_lt-LT.json"
:meta-base "game/assets/jak1/subtitle/subtitle_meta_en-US.json")
)
+
diff --git a/game/assets/jak1/text/game_custom_text_en-US.json b/game/assets/jak1/text/game_custom_text_en-US.json
index 8f38602865..2835e5ab26 100644
--- a/game/assets/jak1/text/game_custom_text_en-US.json
+++ b/game/assets/jak1/text/game_custom_text_en-US.json
@@ -223,5 +223,10 @@
"1614": "UNSET",
"1615": "UNKNOWN",
"1616": "NO OTHER OPTIONS FOR ASPECT RATIO",
- "1617": "CONTROLLER LED FOR HEAT"
+ "1617": "CONTROLLER LED FOR HEAT",
+
+ "2000": "",
+ "2001": "",
+ "2002": "",
+ "2003": ""
}
diff --git a/game/assets/jak2/text/game_custom_text_en-US.json b/game/assets/jak2/text/game_custom_text_en-US.json
index 22e7d0f8c0..089a8b6530 100644
--- a/game/assets/jak2/text/game_custom_text_en-US.json
+++ b/game/assets/jak2/text/game_custom_text_en-US.json
@@ -233,5 +233,10 @@
"1337": "PS2 LOD Distance",
"1338": "Off",
"1339": "Display",
- "133a": " Suomi"
+ "133a": " Suomi",
+
+ "2000": "",
+ "2001": "",
+ "2002": "",
+ "2003": ""
}
diff --git a/game/kernel/common/kmachine.cpp b/game/kernel/common/kmachine.cpp
index 970ae318ec..49ab8daf57 100644
--- a/game/kernel/common/kmachine.cpp
+++ b/game/kernel/common/kmachine.cpp
@@ -1,6 +1,37 @@
#include "kmachine.h"
+#include
+#include
+#include
#include
+#include
+#include
+
+#define MINIAUDIO_IMPLEMENTATION
+// NOTE - this is needed, because on macOS, there is a file called `MacTypes.h`
+// inside it, it defines something named `Ptr`
+// Our `Ptr` is not namespaced, so there is ambiguity.
+//
+// Second fix is because miniaudio redefines functions in the stdlib based on bad pre-processor
+// assumptions AppleClang apparently does not define POSIX macros, leading to future ambiguity
+namespace MiniAudioLib {
+#if defined(__APPLE__)
+#if !defined(_POSIX_C_SOURCE)
+#define _POSIX_C_SOURCE 200809L
+#include "third-party/miniaudio.h"
+#undef _POSIX_C_SOURCE
+#else
+// It should work if it's defined, but for some reason it didn't this is the unlikely branch
+// but lets maintain the original value
+#define NOT_REAL_OLD_POSIX_C_SOURCE _POSIX_C_SOURCE
+#include "third-party/miniaudio.h"
+#define _POSIX_C_SOURCE NOT_REAL_OLD_POSIX_C_SOURCE
+#undef NOT_REAL_OLD_POSIX_C_SOURCE
+#endif
+#else
+#include "third-party/miniaudio.h"
+#endif
+} // namespace MiniAudioLib
#include "common/global_profiler/GlobalProfiler.h"
#include "common/log/log.h"
@@ -44,6 +75,10 @@ u32 vblank_interrupt_handler = 0;
Timer ee_clock_timer;
+MiniAudioLib::ma_engine maEngine;
+std::map> maSoundMap;
+MiniAudioLib::ma_sound* mainMusicSound;
+
void kmachine_init_globals_common() {
memset(pad_dma_buf, 0, sizeof(pad_dma_buf));
isodrv = fakeiso; // changed. fakeiso is the only one that works in opengoal.
@@ -52,6 +87,10 @@ void kmachine_init_globals_common() {
vif1_interrupt_handler = 0;
vblank_interrupt_handler = 0;
ee_clock_timer = Timer();
+#ifdef _WIN32 // only do this on windows, because it only works on windows?
+ MiniAudioLib::ma_engine_uninit(&maEngine);
+#endif
+ MiniAudioLib::ma_engine_init(NULL, &maEngine);
}
/*!
@@ -104,6 +143,163 @@ u64 CPadOpen(u64 cpad_info, s32 pad_number) {
return cpad_info;
}
+// Mutex to synchronize access to activeMusics
+std::mutex activeMusicsMutex;
+
+// Declare a mutex for synchronizing access to mainMusicInstance
+std::mutex mainMusicMutex;
+
+// Function to stop all instances of specific sound by filepath
+void stopMP3(u32 filePathu32) {
+ std::string filePath = Ptr(filePathu32).c()->data();
+ std::cout << "Trying to stop file: " << filePath << std::endl;
+
+ std::lock_guard lock(activeMusicsMutex);
+ auto it = maSoundMap.find(filePath);
+ if (it == maSoundMap.end()) {
+ std::cerr << "Couldn't find sound to stop: " << filePath << std::endl;
+ } else {
+ // stop all instances of this sound
+ for (auto sound : it->second) {
+ if (MiniAudioLib::ma_sound_stop(&sound) != MiniAudioLib::MA_SUCCESS) {
+ std::cerr << "Failed to stop sound: " << filePath << std::endl;
+ }
+ // let the thread finish and handle ma_sound_uninit
+ }
+ // clear list of sounds for this filepath
+ it->second.clear();
+ }
+}
+
+// Function to stop all currently playing sounds.
+void stopAllSounds() {
+ for (auto& pair : maSoundMap) {
+ // stop all instances of this sound
+ for (auto sound : pair.second) {
+ MiniAudioLib::ma_sound_stop(&sound);
+ }
+ pair.second.clear();
+ }
+ maSoundMap.clear();
+}
+
+// Function to get the names of currently playing files.
+std::vector getPlayingFileNames() {
+ std::vector playingFileNames;
+ for (const auto& pair : maSoundMap) {
+ playingFileNames.push_back(pair.first);
+ }
+ return playingFileNames;
+}
+
+void playMP3_internal(u32 filePathu32, u32 volume, bool isMainMusic) {
+ std::thread thread([=]() {
+ std::string filePath = Ptr(filePathu32).c()->data();
+ std::string fullFilePath = fs::path(file_util::get_jak_project_dir() / "custom_assets" /
+ game_version_names[g_game_version] / "audio" / filePath).string();
+
+ std::cout << "Playing file: " << filePath << std::endl;
+
+ MiniAudioLib::ma_result result;
+ MiniAudioLib::ma_sound sound;
+
+ result = MiniAudioLib::ma_sound_init_from_file(&maEngine, fullFilePath.c_str(), 0, NULL, NULL,
+ &sound);
+ if (result != MiniAudioLib::MA_SUCCESS) {
+ std::cout << "Failed to load: " << filePath << std::endl;
+ return;
+ }
+
+ MiniAudioLib::ma_sound_set_volume(&sound, ((float)volume) / 100.0);
+
+ if (isMainMusic) {
+ MiniAudioLib::ma_sound_set_looping(&sound, MA_TRUE);
+ mainMusicMutex.lock();
+ mainMusicSound = &sound;
+ mainMusicMutex.unlock();
+ }
+
+ MiniAudioLib::ma_sound_start(&sound);
+
+ if (!isMainMusic) {
+ std::lock_guard lock(activeMusicsMutex);
+ if (maSoundMap.find(filePath) == maSoundMap.end()) {
+ maSoundMap.insert(std::make_pair(filePath, std::list()));
+ }
+ maSoundMap[filePath].push_back(sound);
+ }
+
+ // sleep/loop until we're no longer main music, or non-looping sound is stopped/ends
+ while (mainMusicSound == &sound || MiniAudioLib::ma_sound_is_playing(&sound)) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
+ }
+
+ MiniAudioLib::ma_sound_stop(&sound);
+ MiniAudioLib::ma_sound_uninit(&sound);
+ std::cout << "Finished playing file: " << filePath << std::endl;
+
+ if (!isMainMusic) {
+ std::lock_guard lock(activeMusicsMutex);
+ if (maSoundMap.find(filePath) != maSoundMap.end()) {
+ maSoundMap[filePath].remove_if(
+ [&](MiniAudioLib::ma_sound l_sound) { return &sound == &l_sound; });
+ }
+ }
+ });
+
+ thread.detach();
+}
+
+void playMP3(u32 filePathu32, u32 volume) {
+ playMP3_internal(filePathu32, volume, false);
+}
+
+// Function to stop the Main Music.
+void stopMainMusic() {
+ mainMusicMutex.lock();
+ if (mainMusicSound && MiniAudioLib::ma_sound_is_playing(mainMusicSound)) {
+ std::cout << "Stopping Main Music..." << std::endl;
+ MiniAudioLib::ma_sound_stop(mainMusicSound);
+ mainMusicSound = NULL;
+ std::cout << "Stopped Main Music " << std::endl;
+ }
+ mainMusicMutex.unlock();
+}
+
+// Function to play the Main Music.
+void playMainMusic(u32 filePathu32, u32 volume) {
+ stopMainMusic();
+
+ std::cout << "Playing Main Music" << std::endl;
+
+ playMP3_internal(filePathu32, volume, true);
+}
+
+void pauseMainMusic() {
+ mainMusicMutex.lock();
+ if (mainMusicSound && MiniAudioLib::ma_sound_is_playing(mainMusicSound)) {
+ MiniAudioLib::ma_sound_stop(mainMusicSound);
+ }
+ mainMusicMutex.unlock();
+}
+
+void resumeMainMusic() {
+ mainMusicMutex.lock();
+ if (mainMusicSound && !MiniAudioLib::ma_sound_is_playing(mainMusicSound)) {
+ MiniAudioLib::ma_sound_start(mainMusicSound);
+ }
+ mainMusicMutex.unlock();
+}
+
+// Function to change the volume of the Main Music.
+void changeMainMusicVolume(u32 volume) {
+ mainMusicMutex.lock();
+ if (mainMusicSound) {
+ MiniAudioLib::ma_sound_set_volume(mainMusicSound, ((float)volume) / 100.0);
+ }
+ mainMusicMutex.unlock();
+}
+
/*!
* Not checked super carefully for jak 2, but looks the same
*/
@@ -994,6 +1190,23 @@ void init_common_pc_port_functions(
make_func_symbol_func("pc-filepath-exists?", (void*)pc_filepath_exists);
make_func_symbol_func("pc-mkdir-file-path", (void*)pc_mkdir_filepath);
+ // Play sound file
+ make_func_symbol_func("play-sound-file", (void*)playMP3);
+
+ // Stop sound file (all instances)
+ make_func_symbol_func("stop-sound-file", (void*)stopMP3);
+
+ // Stop all sounds
+ make_func_symbol_func("stop-all-sounds", (void*)stopAllSounds);
+
+ // Main music stuff
+ make_func_symbol_func("play-main-music", (void*)playMainMusic);
+ make_func_symbol_func("pause-main-music", (void*)pauseMainMusic);
+ make_func_symbol_func("stop-main-music", (void*)stopMainMusic);
+ make_func_symbol_func("resume-main-music", (void*)resumeMainMusic);
+
+ make_func_symbol_func("main-music-volume", (void*)changeMainMusicVolume);
+
// discord rich presence
make_func_symbol_func("pc-discord-rpc-set", (void*)set_discord_rpc);
diff --git a/game/kernel/common/kmachine.h b/game/kernel/common/kmachine.h
index 425c9935d9..010cf1f88e 100644
--- a/game/kernel/common/kmachine.h
+++ b/game/kernel/common/kmachine.h
@@ -85,6 +85,8 @@ struct CommonPCPortFunctionWrappers {
extern CommonPCPortFunctionWrappers g_pc_port_funcs;
+void playMP3(u32 filePath, u32 volume);
+
/// Initializes all common PC Port functions for all Jak games
void init_common_pc_port_functions(
std::function(const char*, void*)> make_func_symbol_func,
diff --git a/game/kernel/jak1/kmachine.cpp b/game/kernel/jak1/kmachine.cpp
index c7730491d9..6be9af2490 100644
--- a/game/kernel/jak1/kmachine.cpp
+++ b/game/kernel/jak1/kmachine.cpp
@@ -601,6 +601,7 @@ void InitMachineScheme() {
make_function_symbol_from_c("kernel-shutdown", (void*)jak1::KernelShutdown); // used TODO jak1
make_function_symbol_from_c("aybabtu", (void*)sceCdMmode); // used
+
InitMachine_PCPort();
InitSoundScheme();
intern_from_c("*stack-top*")->value = 0x07ffc000;
diff --git a/goal_src/goal-lib.gc b/goal_src/goal-lib.gc
index 21073679db..90a3e0d1d9 100644
--- a/goal_src/goal-lib.gc
+++ b/goal_src/goal-lib.gc
@@ -658,6 +658,12 @@
(defmacro false! (var)
`(set! ,var #f))
+(defmacro true? (thing)
+ `(eq? ,thing #t))
+
+(defmacro false? (thing)
+ `(eq? ,thing #f))
+
(defmacro max! (val maxval)
`(set! ,val (max ,val ,maxval)))
(defmacro min! (val minval)
@@ -1275,3 +1281,4 @@
(asm-file "goal_src/jak3/compiler-setup.gc")
)
)
+
diff --git a/goal_src/jak1/dgos/engine.gd b/goal_src/jak1/dgos/engine.gd
index d42ae182ed..80b210b25a 100644
--- a/goal_src/jak1/dgos/engine.gd
+++ b/goal_src/jak1/dgos/engine.gd
@@ -207,6 +207,7 @@
"speedruns.o" ;; added
"pckernel-common.o" ;; added
"pckernel.o" ;; added
+ "input-display.o" ;; mod-base-change added
"mood-tables.o"
"mood.o"
"weather-part.o"
diff --git a/goal_src/jak1/dgos/fic.gd b/goal_src/jak1/dgos/fic.gd
index 323a30b621..9e2ddb09bf 100644
--- a/goal_src/jak1/dgos/fic.gd
+++ b/goal_src/jak1/dgos/fic.gd
@@ -1,10 +1,10 @@
("FIC.DGO"
- ("target-racer-h.o"
- "racer-part.o"
- "racer.o"
- "target-racer.o"
- "racer-states.o"
- "collide-reaction-racer.o"
+ (;;"target-racer-h.o" mod-base-change
+ ;;"racer-part.o"
+ ;;"racer.o"
+ ;;"target-racer.o"
+ ;;"racer-states.o"
+ ;;"collide-reaction-racer.o"
"eichar-racer+0-ag.go"
"tpage-1119.go"
"blocking-plane.o"
diff --git a/goal_src/jak1/dgos/game.gd b/goal_src/jak1/dgos/game.gd
index 640e81b954..4478e95203 100644
--- a/goal_src/jak1/dgos/game.gd
+++ b/goal_src/jak1/dgos/game.gd
@@ -203,6 +203,7 @@
"speedruns.o" ;; added
"pckernel-common.o" ;; added
"pckernel.o" ;; added
+ "input-display.o" ;; mod-base-change added
"mood-tables.o"
"mood.o"
"weather-part.o"
@@ -346,4 +347,28 @@
"ropebridge.o"
"ticky.o"
"hud-classes-pc.o" ;; added
+ "mod-settings.o" ;; added mod-base-change
+ "mod-common-functions.o" ;; added
+ "orb-placer.o" ;; added
+ "mod-custom-code.o" ;; added
+ "mod-debug.o" ;; added
+ ;; keep zoomer stuff loaded
+ "target-racer-h.o"
+ "racer-part.o"
+ "racer.o"
+ "target-racer.o"
+ "racer-states.o"
+ "collide-reaction-racer.o"
+ "racer-ag.go"
+ "eichar-racer+0-ag.go"
+ "tpage-1119.go"
+ ;; keep flutflut stuff loaded
+ "flut-part.o"
+ "flutflut.o"
+ "target-flut.o"
+ "flut-saddle-ag.go"
+ "eichar-flut+0-ag.go"
+ ;; keep blocking-plane stuff loaded
+ "blocking-plane.o"
+ "ef-plane-ag.go"
))
diff --git a/goal_src/jak1/dgos/lav.gd b/goal_src/jak1/dgos/lav.gd
index 7f1a1fb8c7..6fb3ed6b4f 100644
--- a/goal_src/jak1/dgos/lav.gd
+++ b/goal_src/jak1/dgos/lav.gd
@@ -1,10 +1,10 @@
("LAV.DGO"
- ("target-racer-h.o"
- "racer-part.o"
- "racer.o"
- "target-racer.o"
- "racer-states.o"
- "collide-reaction-racer.o"
+ (;;"target-racer-h.o" mod-base-change
+ ;;"racer-part.o"
+ ;;"racer.o"
+ ;;"target-racer.o"
+ ;;"racer-states.o"
+ ;;"collide-reaction-racer.o"
"eichar-racer+0-ag.go"
"tpage-1119.go"
"blocking-plane.o"
diff --git a/goal_src/jak1/dgos/mis.gd b/goal_src/jak1/dgos/mis.gd
index 981be01bf4..059a89ef71 100644
--- a/goal_src/jak1/dgos/mis.gd
+++ b/goal_src/jak1/dgos/mis.gd
@@ -1,10 +1,10 @@
("MIS.DGO"
- ("target-racer-h.o"
- "racer-part.o"
- "racer.o"
- "target-racer.o"
- "racer-states.o"
- "collide-reaction-racer.o"
+ (;;"target-racer-h.o" mod-base-change
+ ;;"racer-part.o"
+ ;;"racer.o"
+ ;;"target-racer.o"
+ ;;"racer-states.o"
+ ;;"collide-reaction-racer.o"
"eichar-racer+0-ag.go"
"tpage-1119.go"
"battlecontroller.o"
diff --git a/goal_src/jak1/dgos/ogr.gd b/goal_src/jak1/dgos/ogr.gd
index 34df743238..7c4cf527de 100644
--- a/goal_src/jak1/dgos/ogr.gd
+++ b/goal_src/jak1/dgos/ogr.gd
@@ -1,10 +1,10 @@
("OGR.DGO"
- ("target-racer-h.o"
- "racer-part.o"
- "racer.o"
- "target-racer.o"
- "racer-states.o"
- "collide-reaction-racer.o"
+ (;;"target-racer-h.o" mod-base-change
+ ;;"racer-part.o"
+ ;;"racer.o"
+ ;;"target-racer.o"
+ ;;"racer-states.o"
+ ;;"collide-reaction-racer.o"
"eichar-racer+0-ag.go"
"tpage-1119.go"
"blocking-plane.o"
diff --git a/goal_src/jak1/dgos/rol.gd b/goal_src/jak1/dgos/rol.gd
index f108ba9ef7..c46aad2bb4 100644
--- a/goal_src/jak1/dgos/rol.gd
+++ b/goal_src/jak1/dgos/rol.gd
@@ -1,10 +1,10 @@
("ROL.DGO"
- ("target-racer-h.o"
- "racer-part.o"
- "racer.o"
- "target-racer.o"
- "racer-states.o"
- "collide-reaction-racer.o"
+ (;;"target-racer-h.o" mod-base-change
+ ;;"racer-part.o"
+ ;;"racer.o"
+ ;;"target-racer.o"
+ ;;"racer-states.o"
+ ;;"collide-reaction-racer.o"
"eichar-racer+0-ag.go"
"tpage-1119.go"
"blocking-plane.o"
diff --git a/goal_src/jak1/dgos/sno.gd b/goal_src/jak1/dgos/sno.gd
index bbc161f3db..f6fcc23219 100644
--- a/goal_src/jak1/dgos/sno.gd
+++ b/goal_src/jak1/dgos/sno.gd
@@ -1,8 +1,8 @@
("SNO.DGO"
("blocking-plane.o"
- "flut-part.o"
- "flutflut.o"
- "target-flut.o"
+ ;; "flut-part.o"
+ ;; "flutflut.o"
+ ;; "target-flut.o"
"eichar-flut+0-ag.go"
"target-snowball.o"
"target-ice.o"
@@ -50,4 +50,4 @@
"snowpusher-ag.go"
"yeti-ag.go"
"snow-vis.go"
- ))
\ No newline at end of file
+ ))
diff --git a/goal_src/jak1/dgos/swa.gd b/goal_src/jak1/dgos/swa.gd
index 8527e59b8b..c8f38b0bd0 100644
--- a/goal_src/jak1/dgos/swa.gd
+++ b/goal_src/jak1/dgos/swa.gd
@@ -1,9 +1,9 @@
("SWA.DGO"
("battlecontroller.o"
"blocking-plane.o"
- "flut-part.o"
- "flutflut.o"
- "target-flut.o"
+ ;; "flut-part.o"
+ ;; "flutflut.o"
+ ;; "target-flut.o" mod-base-change
"eichar-flut+0-ag.go"
"swamp-obs.o"
"swamp-bat.o"
diff --git a/goal_src/jak1/engine/anim/joint.gc b/goal_src/jak1/engine/anim/joint.gc
index 71d216530d..92ced4858d 100644
--- a/goal_src/jak1/engine/anim/joint.gc
+++ b/goal_src/jak1/engine/anim/joint.gc
@@ -693,8 +693,12 @@
((not (file-info-correct-version? (-> this info) (file-kind art-group) 0)) (the-as art-group #f))
(else
(let ((s5-1 (-> *level* loading-level)))
- (if (or (not s5-1) (= (-> s5-1 name) 'default))
+ (when (or (not s5-1) (= (-> s5-1 name) 'default))
(login this) ;; not part of level load, just normal login.
+ (when (needs-link? this)
+ (format 0 "bonus link~%")
+ (link-art! this)
+ )
)
(if s5-1
(set-loaded-art (-> s5-1 art-group) this) ;; part of level load, add to level's ag, but don't log in yet.
diff --git a/goal_src/jak1/engine/common-obs/collectables.gc b/goal_src/jak1/engine/common-obs/collectables.gc
index e2ff87d473..96105d5110 100644
--- a/goal_src/jak1/engine/common-obs/collectables.gc
+++ b/goal_src/jak1/engine/common-obs/collectables.gc
@@ -7,6 +7,21 @@
(require "pc/pckernel.gc")
(require "engine/common-obs/collectables-part.gc")
(require "engine/entity/entity.gc")
+
+;; name: collectables.gc
+;; name in dgo: collectables
+;; dgos: GAME, ENGINE
+
+(define-extern runs-on-orb-pickup (function process-tree none))
+
+(define-extern runs-on-fly-pickup (function none))
+
+(define-extern runs-on-cell-pickup (function symbol none))
+
+(define-extern runs-on-eco-pickup (function pickup-type process-tree none))
+
+(define-extern orb-placer-list-maintenace (function symbol none))
+
(declare-type vent process-drawable)
(declare-type eco eco-collectable)
@@ -485,6 +500,7 @@
(level-hint-spawn (text-id training-eco-reminder) "sagevb23" (the-as entity #f) *entity-pool* (game-task none)))))
(((pickup-type eco-green)) (sound-play "g-eco-pickup"))
(((pickup-type eco-green) (pickup-type eco-pill)) (sound-play "pill-pickup")))
+ (runs-on-eco-pickup (-> self fact pickup-type) (ppointer->process (-> self parent))) ;; trigger hook for mod base
(if (nonzero? (-> self part)) (kill-and-free-particles (-> self part)))
(let ((gp-6 (handle->process (-> self pickup-handle))))
(if (nonzero? (-> self collect-effect))
@@ -726,7 +742,13 @@
(logclear! (-> self mask) (process-mask actor-pause))
(clear-collide-with-as (-> self root))
(process-entity-status! self (entity-perm-status dead) #t)
- (convert-to-hud-object self (the-as hud (ppointer->process (-> *hud-parts* money))))))
+ (runs-on-orb-pickup (ppointer->process (-> self parent))) ;; trigger hook for mod base
+ (convert-to-hud-object self (the-as hud (ppointer->process (-> *hud-parts* money)))))
+ :exit
+ (behavior ()
+ ;; refresh orb-placer list in debug mode
+ (when *debug-segment*
+ (orb-placer-list-maintenace #t))))
(defmethod initialize ((this money))
(stack-size-set! (-> this main-thread) 192) ;; og:preserve-this hack increased from 128
@@ -1085,6 +1107,7 @@
(((game-task citadel-sage-green)) (send-event (process-by-name "green-sagecage-1" *active-pool*) 'play-anim))
(((game-task jungle-eggtop) (game-task snow-eggtop) (game-task snow-ball) (game-task sunken-slide)))
(else (send-event (ppointer->process (-> *hud-parts* fuel-cell)) 'show)))
+ (runs-on-cell-pickup 'cutscene-end)
(convert-to-hud-object self (the-as hud (ppointer->process (-> *hud-parts* fuel-cell)))))
:post
(behavior ()
@@ -1261,6 +1284,7 @@
(logclear! (-> self mask) (process-mask actor-pause))
(process-entity-status! self (entity-perm-status complete) #t)
(sound-play "buzzer-pickup")
+ (runs-on-fly-pickup)
(case (-> (level-get-target-inside *level*) name)
(('training)
(level-hint-spawn (text-id training-buzzer-hint) "asstvb44" (the-as entity #f) *entity-pool* (game-task none)))
@@ -1714,6 +1738,7 @@
(set! (-> a1-1 message) 'get-pickup)
(set! (-> a1-1 param 0) (the-as uint (-> self fact pickup-type)))
(set! (-> a1-1 param 1) (the-as uint (-> self fact pickup-amount)))
+ (runs-on-eco-pickup (-> self fact pickup-type) self)
(and (= (send-event-function proc a1-1) #t)
(or (logtest? (-> self fact options) (fact-options powerup)) (send-event proc 'powerup)))))
(go vent-pickup (process->handle proc)))
diff --git a/goal_src/jak1/engine/common-obs/linear-plat.gc b/goal_src/jak1/engine/common-obs/linear-plat.gc
new file mode 100644
index 0000000000..7050fc62ab
--- /dev/null
+++ b/goal_src/jak1/engine/common-obs/linear-plat.gc
@@ -0,0 +1,126 @@
+;;-*-Lisp-*-
+(in-package goal)
+(bundles "GAME.CGO")
+(require "engine/common-obs/generic-obs.gc")
+(require "engine/geometry/path.gc")
+(require "engine/util/sync-info.gc")
+(require "engine/common-obs/plat.gc")
+
+#| custom platform for complex animations
+
+ define path array of vector4m as usual, probably you want first and last positions to be the same
+ "path": ["vector4m",
+ [0.0, 50.0, 10.0, 1.0],
+ [0.0, 50.0, 10.0, 1.0],
+ [0.0, 60.0, 10.0, 1.0],
+ [0.0, 50.0, 10.0, 1.0],
+ [0.0, 50.0, 10.0, 1.0],
+ [0.0, 60.0, 10.0, 1.0],
+ [0.0, 50.0, 10.0, 1.0]
+ ],
+
+ define timings array, which says how long in seconds it should take to transition between path positions
+ "timings": ["float",
+ 0.0, // offset before starting
+ 2.0, // down to up (out)
+ 2.0, // up to down (out)
+ 6.0, // stay down
+ 2.0, // down to up (back)
+ 2.0, // up to down (back)
+ 0.0 // immediately start back up
+ ],
+
+ define sync with proper period (duration of the whole path). offset is ignored
+ "sync": ["float", 14, 0.0]
+|#
+(deftype linear-plat (plat)
+ ((timings float 64))) ;; inline array of floats
+
+(defun get-current-phase-linear-plat ((this linear-plat))
+ (let* ((cur-time (mod (the-as uint (current-time)) (-> this sync period)))
+ (pos 0))
+ (while (< pos (-> this path curve num-cverts))
+ (let ((timing (fsec (-> this timings pos))))
+ (when (< cur-time timing)
+ ;; we should be between pos and pos+1
+ (return (+ (the float pos) (/ (the float cur-time) (the float timing)))))
+
+ ;; we are at least at next position
+ (+! pos 1)
+ (-! cur-time timing))))
+ 0.0 ;; default
+ )
+
+(defun eval-linear-path! ((path path-control) (basetrans vector) (path-pos float))
+ (let* ((pos0 (the int path-pos))
+ (pos1 (if (>= (+ pos0 1) (-> path curve num-cverts)) 0 (+ pos0 1)))
+ (partial (- path-pos pos0))
+ (dir (new-stack-vector0)))
+ ;; vector from pos0 to pos1
+ (vector-! dir (-> path curve cverts pos1) (-> path curve cverts pos0))
+ ;; partial vector based on path-pos decimal
+ (vector-float*! dir dir partial)
+ ;; update basetrans
+ (vector+! basetrans (-> path curve cverts pos0) dir)
+ )
+ )
+
+(defstate plat-path-active (linear-plat)
+ :virtual #t
+ :event plat-event
+ :exit
+ (behavior ()
+ (sound-stop (-> self sound-id)))
+ :trans
+ (behavior ()
+ (set! (-> self path-pos)
+ (get-current-phase-linear-plat self))
+ (eval-linear-path! (-> self path) (-> self basetrans) (-> self path-pos))
+ (if (< (vector-vector-distance (-> self root trans) (ear-trans)) 81920.0)
+ (sound-play "eco-plat-hover" :id (-> self sound-id) :position (the-as symbol (-> self root trans))))
+ (plat-trans))
+ :code
+ (the-as (function plat object)
+ anim-loop)
+ :post plat-post)
+
+(defmethod init-from-entity! ((this linear-plat) (arg0 entity-actor))
+ (logior! (-> this mask) (process-mask platform))
+ (baseplat-method-24 this)
+ (process-drawable-from-entity! this arg0)
+ (initialize-skeleton this (get-unlit-skel this) '())
+ (logior! (-> this skel status) (janim-status inited))
+ (update-transforms! (-> this root))
+ (baseplat-method-21 this)
+ (baseplat-method-25 this)
+ (load-params! (-> this sync) this (the-as uint 0) 0.0 0.15 0.15)
+ (set! (-> this fact) (new 'process 'fact-info this (pickup-type eco-pill-random) (-> *FACT-bank* default-pill-inc)))
+ (set! (-> this path) (new 'process 'curve-control this 'path -1000000000.0))
+
+ ;; read path timing data from res-lump
+ ;; look up the curve data
+ (let* ((tag (new 'static 'res-tag))
+ (data (res-lump-data arg0 'timings pointer :tag-ptr (& tag) :time -1000000000.0)))
+ (when data
+ ;; success, we got some data
+ (dotimes (idx (-> this path curve num-cverts))
+ (set! (-> this timings idx) (-> (the-as (pointer float) data) idx)))))
+
+ (logior! (-> this path flags) (path-control-flag display draw-line draw-point draw-text))
+ (set! (-> this sound-id) (new-sound-id))
+ (cond
+ ((logtest? (-> this path flags) (path-control-flag not-found))
+ (set! (-> this path-pos) 0.0)
+ (let ((a0-14 this)) (baseplat-method-26 a0-14) (go (method-of-object this plat-startup) a0-14)))
+ ((> (-> this sync period) 0)
+ (set! (-> this path-pos)
+ (if (logtest? (-> this fact options) (fact-options wrap-phase))
+ (get-current-phase (-> this sync))
+ (get-current-phase-with-mirror (-> this sync))))
+ (eval-path-curve! (-> this path) (-> this root trans) (-> this path-pos) 'interp)
+ (let ((a0-18 this)) (baseplat-method-26 a0-18) (go (method-of-object this plat-startup) a0-18)))
+ (else
+ (set! (-> this path-pos) 0.0)
+ (eval-path-curve! (-> this path) (-> this root trans) (-> this path-pos) 'interp)
+ (let ((a0-20 this)) (baseplat-method-26 a0-20) (go (method-of-object this plat-startup) a0-20))))
+ (none))
diff --git a/goal_src/jak1/engine/debug/default-menu.gc b/goal_src/jak1/engine/debug/default-menu.gc
index 341c997ed9..8de974620c 100644
--- a/goal_src/jak1/engine/debug/default-menu.gc
+++ b/goal_src/jak1/engine/debug/default-menu.gc
@@ -2403,6 +2403,7 @@
(debug-menu-append-item s5-0
(debug-menu-make-from-template arg0
'(menu "Display"
+ (flag "Input Display" *show-input-display* dm-boolean-toggle-pick-func)
(flag "Profile" *display-profile* dm-boolean-toggle-pick-func)
(flag "Ticks" *profile-ticks* dm-boolean-toggle-pick-func)
(flag "File Info" *display-file-info* dm-boolean-toggle-pick-func)
@@ -2418,6 +2419,7 @@
(flag "Camera Info" *display-camera-info* dm-boolean-toggle-pick-func)
(flag "Geometry Marks" *display-geo-marks* dm-boolean-toggle-pick-func)
(flag "Nav Marks" *display-nav-marks* dm-boolean-toggle-pick-func)
+ (flag "Nav Marks Extras" *display-nav-marks-extras* dm-boolean-toggle-pick-func)
(flag "Path Marks" *display-path-marks* dm-boolean-toggle-pick-func)
(flag "Vol Marks" *display-vol-marks* dm-boolean-toggle-pick-func)
(menu "Ambient Marks"
@@ -2532,6 +2534,7 @@
(menu "Mode"
(function "normal" #f ,(lambda () (send-event *target* 'end-mode)))
(function "racing" #f ,(lambda () (send-event *target* 'change-mode 'racing #f)))
+ (function "flut" #f ,(lambda () (send-event *target* 'change-mode 'flut #f)))
(function "snowball" #f ,(lambda () (send-event *target* 'change-mode 'snowball #f))))
(flag "Slow Frame Rate" *slow-frame-rate* dm-boolean-toggle-pick-func)
(function "Print Pos"
diff --git a/goal_src/jak1/engine/entity/entity.gc b/goal_src/jak1/engine/entity/entity.gc
index d79d0865e0..2713c1e385 100644
--- a/goal_src/jak1/engine/entity/entity.gc
+++ b/goal_src/jak1/engine/entity/entity.gc
@@ -166,6 +166,10 @@
"Get the process for the entity with the given name. If there is no entity or process, #f."
(let ((v1-0 (entity-by-name arg0))) (if v1-0 (-> v1-0 extra process))))
+(defun process-drawable-by-ename ((arg0 string))
+ "Get the process-drawable for the entity with the given name. If there is no entity or process, #f."
+ (let ((v1-0 (entity-by-name arg0))) (if v1-0 (the-as process-drawable (-> v1-0 extra process)))))
+
(defun entity-process-count ((arg0 symbol))
"Count the number of entities with a process. If arg0 is 'vis, will count visible entities."
(let ((gp-0 0))
@@ -821,6 +825,91 @@
(defmacro this-etype? (&rest types)
`(or ,@(apply (lambda (x) `(begin (define-extern ,x type) (type-type? (-> this etype) ,x))) types)))
+(defun custom-nav-mesh-check-and-setup ((this entity-actor))
+ (case (-> this aid)
+ ((40000) ;; test-babak
+ (set! (-> this nav-mesh)
+ (new 'static
+ 'nav-mesh
+ :custom-hacky? #t
+ ;; Our hack. This will neutralize the function that caused trouble.
+ :origin
+ (new 'static 'vector :x (meters -7.2681) :y (meters 1.0468) :z (meters 16.3255) :w 1.0000) ;; Location of the navmesh.
+ :bounds
+ (new 'static 'sphere :x (meters -7.2681) :y (meters 1.0468) :z (meters 16.3255) :w (meters 20.0)) ;; Some kind of boundary. It should cover the whole navmesh. Enemies won't go to parts it doesn't cover.
+ :vertex-count 4
+ ;; If this is missing, then debug drawing of the vertexes will not be possible.
+ :vertex
+ (new 'static
+ 'inline-array
+ nav-vertex
+ 4 ;; The vertexes, we can use these to form our triangles.
+ (new 'static 'nav-vertex :x (meters 5.8046) :y (meters 0.1945) :z (meters 6.6462) :w 1.0000) ;; All coordinates are relative to the origin of the navmesh...
+ (new 'static 'nav-vertex :x (meters 5.5363) :y (meters 0.4561) :z (meters -6.1448) :w 1.0000) ;; ...which is good news for us, we can easily copy navmeshes and just change the origin (and bounds).
+ (new 'static 'nav-vertex :x (meters -4.8047) :y (meters -0.1945) :z (meters -6.6463) :w 1.0000)
+ (new 'static 'nav-vertex :x (meters -4.5910) :y (meters -0.5481) :z (meters 6.0990) :w 1.0000))
+ :poly-count 2
+ ;; For some weird reason, you need to define this, otherwise it will think that there are 0 polys. Even though it could just tell from the array below...
+ :poly
+ (new 'static
+ 'inline-array
+ nav-poly
+ 2 ;; Here we define our polygons (only triangles).
+ (new 'static
+ 'nav-poly
+ :id #x0
+ ;; It needs a unique ID, because we need to refer to it at some places. It is possible that this gets assigned automatically if you don't write it here. But it is better to have it here for clarity.
+ :vertex
+ (new 'static 'array uint8 3 #x0 #x1 #x3) ;; You pick your 3 vertexes here, from among the ones we defined earlier. You need to pick them in an okay order. Because of the normals. Logs will warn you if it is wrong.
+ :adj-poly
+ (new 'static 'array uint8 3 #xff #x1 #xff) ;; You can (or should) define any adjecent triangles here. I am sure this is important. The order may or may not matter - I tried to respect the order here.
+ ;;:pat 0 ;; This is 0 by default. The so called "gap triangles" have it set to 1. But to get gap triangles working properly, you need proper route data. Which we have in existing navmeshes, but not in fully custom ones.
+ )
+ (new 'static
+ 'nav-poly
+ :id #x1
+ :vertex
+ (new 'static 'array uint8 3 #x1 #x2 #x3)
+ :adj-poly
+ (new 'static 'array uint8 3 #xff #xff #x0)))
+ ;; Optional. I don't understand this. It might impact enemy behaviour/movement on the navmesh, but not sure how. Even the smallest navmesh has at least one of these.
+ ;; Commenting it out, but leaving it here because it may be useful for something.
+ ;; Also I probably have redundancy among the properties (for example I am not sure if radius-x is needed if we have radius, and vice-versa).
+ #|
+:nodes (new 'static 'inline-array nav-node 1
+ (new 'static 'nav-node :center-x (meters 0.4020) :center-y (meters -0.2929) :center-z (meters -0.0592)
+ :type 1
+ :parent-offset 0
+ :center (new 'static 'vector :x (meters 0.4020) :y (meters -0.2929) :z (meters -0.0592) :w 0.0000)
+ :radius-x (meters 20.6370)
+ :radius-y (meters 0.3081)
+ :radius-z (meters 20.6425)
+ :radius (new 'static 'vector :x (meters 20.6370) :y (meters 0.3081) :z (meters 20.6425) :w 0.0000)
+ :left-offset 2
+ :right-offset 0
+ :num-tris 2
+ :scale-x (meters 0.1309)
+ :scale-z (meters 0.1157)
+ :scale (new 'static 'vector :x (meters 30.1309) :y 0.0000 :z (meters 3.1157) :w 0.0000)
+ :first-tris (new 'static 'array uint8 4 #x0 #x1 #x0 #x0)
+ :last-tris (new 'static 'array uint8 4 #x0 #x0 #x0 #x0))
+ )
+ |#
+ ;; Route is essential (game crashes without it), Not sure how it works exactly. Also not sure how to determine the array length. Seems to be poly-count*2?
+ ;; It is needed if you want the gap triangles to work. But it seems gibberish even in existing navmeshes. Maybe the type is wrong?
+ ;; The simplest navmeshese have this exact same route data as seen below, so I am using it in our example.
+ :route
+ (new 'static
+ 'inline-array
+ vector4ub
+ 4
+ (new 'static 'vector4ub :data (new 'static 'array uint8 4 #xff #x0 #x0 #x0))
+ (new 'static 'vector4ub :data (new 'static 'array uint8 4 #x0 #x0 #x0 #x0))
+ (new 'static 'vector4ub :data (new 'static 'array uint8 4 #x0 #x0 #x0 #x0))
+ (new 'static 'vector4ub :data (new 'static 'array uint8 4 #x0 #x0 #x0 #x0)))))
+ (entity-nav-login this) ;; I don't need to know what this does, all I know is that this is needed - the navmesh won't be okay without it.
+ )))
+
(defmethod birth! ((this entity-actor))
"Create a process for this entity and start it."
;; temp
@@ -835,6 +924,7 @@
;; (return this)
;; )
;;(birth-log "call to birth! on ~A~%" this)
+ (custom-nav-mesh-check-and-setup this)
(let* ((entity-type (-> this etype))
(info (entity-info-lookup entity-type))
(entity-process (get-process *default-dead-pool* entity-type (if info (-> info heap-size) #x4000))))
diff --git a/goal_src/jak1/engine/game/game-info-h.gc b/goal_src/jak1/engine/game/game-info-h.gc
index c193c40dd7..2b8bde64c5 100644
--- a/goal_src/jak1/engine/game/game-info-h.gc
+++ b/goal_src/jak1/engine/game/game-info-h.gc
@@ -191,7 +191,7 @@
(when (or (not *game-info*) (zero? *game-info*))
(let ((temp (new 'static 'game-info :mode 'debug :current-continue #f)))
- (set! (-> temp fuel-cell-time) (new 'global 'boxed-array time-frame 116))
+ (set! (-> temp fuel-cell-time) (new 'global 'boxed-array time-frame (the int (game-task max)))) ;; mod-base-change changed from hard-coded 116 to (game-task max)
(set! (-> temp enter-level-time) (new 'global 'boxed-array time-frame 32))
(set! (-> temp in-level-time) (new 'global 'boxed-array time-frame 32))
(set! *game-info* temp)))
diff --git a/goal_src/jak1/engine/game/game-info.gc b/goal_src/jak1/engine/game/game-info.gc
index 3a041b7841..60c73d41bf 100644
--- a/goal_src/jak1/engine/game/game-info.gc
+++ b/goal_src/jak1/engine/game/game-info.gc
@@ -20,6 +20,13 @@
;; DECOMP BEGINS
+(define-extern runs-on-jak-death (function symbol none))
+
+;;Forward declaration Variable for mod base settings
+(define-extern startingDebugContinuePoint (string))
+
+;; DECOMP BEGINS
+
;;;;;;;;;;;;;;;;;;
;; border plane
;;;;;;;;;;;;;;;;;;
@@ -167,6 +174,7 @@
;; not in play mode, we're done.
(set! this this)
(goto cfg-50)))))
+ (runs-on-jak-death 'blackout) ;;mod-base-change
(kill-current-level-hint '() '() 'die)
(case cause
(('game)
@@ -177,6 +185,7 @@
(cond
(continue-point-override (empty) continue-point-override)
((!= *kernel-boot-message* 'play) "demo-start")
+ ((and *debug-segment* (not (string= startingDebugContinuePoint "village1-hut"))) startingDebugContinuePoint) ;; mod-base-change
(*debug-segment* "village1-hut")
(else "title-start")))
(set! (-> this auto-save-count) 0)
@@ -197,7 +206,7 @@
(set-time! (-> this continue-time))
(set-time! (-> this death-time))
(set-time! (-> this hit-time))
- (dotimes (v1-50 116)
+ (dotimes (v1-50 (the int (game-task max))) ;; changed from hard-coded 116 to (game-task max) mod-base-change
(set! (-> this fuel-cell-time 0) 0)
(nop!))
(dotimes (v1-53 32)
@@ -654,7 +663,7 @@
(inspect this)
(when (or (not arg0) (= arg0 'game-task))
(format #t "~Tgame-task:~%")
- (dotimes (s4-0 116)
+ (dotimes (s4-0 (the int (game-task max))) ;; changed from hard-coded 116 to (game-task max) ;; mod-base-change
(if (task-complete? this (the-as game-task s4-0)) (format #t "~T~T~S~%" (game-task->string (the-as game-task s4-0))))))
(when (or (not arg0) (= arg0 'entity-perm))
(format #t "~Tentity-perm:~%")
@@ -672,7 +681,7 @@
0)
;; task perms
(when (zero? (-> gp-0 task-perm-list))
- (let ((v1-15 (new 'global 'entity-perm-array 116)))
+ (let ((v1-15 (new 'global 'entity-perm-array (the int (game-task max))))) ;; mod-base-change changed from hard-coded 116 to (game-task max)
(set! (-> gp-0 task-perm-list) v1-15)
(dotimes (a0-24 (-> v1-15 length))
(set! (-> v1-15 data a0-24 task) (the-as game-task a0-24)))
diff --git a/goal_src/jak1/engine/game/game-save.gc b/goal_src/jak1/engine/game/game-save.gc
index 84db8e75d0..362ed99fa5 100644
--- a/goal_src/jak1/engine/game/game-save.gc
+++ b/goal_src/jak1/engine/game/game-save.gc
@@ -491,11 +491,11 @@
(let ((v1-95 (&+ v1-94 16)))
(let ((a0-63 (the-as game-save-tag (&+ v1-95 0))))
(set! (-> a0-63 elt-type) (game-save-elt fuel-cell-time))
- (set! (-> a0-63 elt-count) 116)
+ (set! (-> a0-63 elt-count) (the int (game-task max))) ;; changed from hard-coded 116 to (game-task max) mod-base-change
(set! (-> a0-63 elt-size) (the-as uint 8)))
(let ((v1-96 (&+ v1-95 16)))
(let ((a0-64 (the-as object 0)))
- (while (< (the-as int a0-64) 116)
+ (while (< (the-as int a0-64) (the int (game-task max))) ;; changed from hard-coded 116 to (game-task max) mod-base-change
(set! (-> (the-as (pointer int64) (&+ v1-96 (* (the-as int a0-64) 8)))) (-> this fuel-cell-time (the-as int a0-64)))
(set! a0-64 (+ (the-as int a0-64) 1))))
(let ((v1-97 (&+ v1-96 928)))
@@ -720,7 +720,7 @@
(let ((a1-68 (-> *level* level s4-1))) (if (= (-> a1-68 status) 'active) (copy-perms-to-level! this a1-68))))
;; update tasks
(let ((s5-1 2) ;; jungle eggtop
- (s4-2 115) ;; max - 1
+ (s4-2 (- (the int (game-task max)) 1)) ;; max - 1 mod-base-change changed to use enum
)
(while (>= (the-as uint s4-2) (the-as uint s5-1))
;; calling this will run the function to see if the task is done or not
diff --git a/goal_src/jak1/engine/game/main-h.gc b/goal_src/jak1/engine/game/main-h.gc
index a3f5d03bc4..f8c24668b5 100644
--- a/goal_src/jak1/engine/game/main-h.gc
+++ b/goal_src/jak1/engine/game/main-h.gc
@@ -128,6 +128,8 @@
(define *display-nav-marks* #f)
+(define *display-nav-marks-extras* #f)
+
(define *display-path-marks* #f)
(define *display-vol-marks* #f)
diff --git a/goal_src/jak1/engine/game/main.gc b/goal_src/jak1/engine/game/main.gc
index d06326de0a..0381ae9cd0 100644
--- a/goal_src/jak1/engine/game/main.gc
+++ b/goal_src/jak1/engine/game/main.gc
@@ -15,6 +15,7 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Letterbox and blackout
;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun-extern runs-every-frame (none))
(defun set-letterbox-frames ((arg0 time-frame))
"Set the letterbox frame counter for arg0 frames in the future"
@@ -356,6 +357,7 @@
;; collide dma
(suspend)
(while *run*
+ (runs-every-frame) ;; trigger hook for mod base mod-base-change entry point
;; start immediately after all process updates finish.
(profiler-instant-event "display-loop-top")
;; drawing effects to be used in foreground drawing.
diff --git a/goal_src/jak1/engine/game/settings.gc b/goal_src/jak1/engine/game/settings.gc
index e9f2515d79..12a7545048 100644
--- a/goal_src/jak1/engine/game/settings.gc
+++ b/goal_src/jak1/engine/game/settings.gc
@@ -259,9 +259,9 @@
(set! (-> gp-0 process-mask) (process-mask execute sleep))
(set! (-> gp-0 screenx) 0)
(set! (-> gp-0 screeny) 0)
- (set! (-> gp-0 vibration) #t)
+ (set! (-> gp-0 vibration) #f) ;; mod-base-change
(set! (-> gp-0 auto-save) #f)
- (set! (-> gp-0 play-hints) #t)
+ (set! (-> gp-0 play-hints) #f) ;; mod-base-change
(set! (-> gp-0 movie) (the-as (pointer process) #f))
(set! (-> gp-0 talking) (the-as (pointer process) #f))
(set! (-> gp-0 spooling) (the-as (pointer process) #f))
diff --git a/goal_src/jak1/engine/game/task/task-control.gc b/goal_src/jak1/engine/game/task/task-control.gc
index 9322fcfd20..2c538f5773 100644
--- a/goal_src/jak1/engine/game/task/task-control.gc
+++ b/goal_src/jak1/engine/game/task/task-control.gc
@@ -4977,7 +4977,7 @@
(defun get-task-control ((arg0 game-task))
"Get the task control for a given game-task"
(cond
- ((or (>= (the-as uint 1) (the-as uint arg0)) (>= (the-as uint arg0) (the-as uint 116)))
+ ((or (>= (the-as uint 1) (the-as uint arg0)) (>= (the-as uint arg0) (the-as uint (game-task max)))) ;; mod-base-chage changed from hard-coded 116 to (game-task max)
;; invalid game task
(format 0 "ERROR: get-task-control received invalid task ~D/~S~%" arg0 (game-task->string arg0))
*null-task-control*)
diff --git a/goal_src/jak1/engine/level/level.gc b/goal_src/jak1/engine/level/level.gc
index c34f6976af..bcbb40c5f3 100644
--- a/goal_src/jak1/engine/level/level.gc
+++ b/goal_src/jak1/engine/level/level.gc
@@ -705,6 +705,38 @@
;; method 10 level
(defmethod is-object-visible? ((this level) (arg0 int))
"Is arg0 visible? Note that this will return #f if the visibility data is not loaded."
+ ;; og:mod-base anything thats not a vanilla level -> always show actors
+ (case (-> this name)
+ :comp
+ name=
+ (('intro 'title
+ 'demo
+ 'training
+ 'village1
+ 'beach
+ 'jungle
+ 'jungleb
+ 'misty
+ 'firecanyon
+ 'village2
+ 'sunken
+ 'sunkenb
+ 'rolling
+ 'swamp
+ 'ogre
+ 'village3
+ 'snow
+ 'maincave
+ 'darkcave
+ 'robocave
+ 'lavatube
+ 'citadel
+ 'finalboss)
+ ;; vanilla level -> fall through to normal checks
+ )
+ (else
+ ;; some custom level -> return true
+ (return #t)))
;; og:preserve-this pc port added option to show every actor regardless
(with-pc
(if (not (-> *pc-settings* ps2-actor-vis?)) (return #t)))
diff --git a/goal_src/jak1/engine/mods/input-display.gc b/goal_src/jak1/engine/mods/input-display.gc
new file mode 100644
index 0000000000..fab08d6e34
--- /dev/null
+++ b/goal_src/jak1/engine/mods/input-display.gc
@@ -0,0 +1,480 @@
+;;-*-Lisp-*-
+(in-package goal)
+
+#|
+An input display written in GOAL
+
+Controller joystick locations have text coordinates and a grid display
+
+Face buttons were added to game_custom_text so they could be displayed with lookup-text!
+Other buttons are displayed using text: "R3" "Select"
+
+Sections of the input display are anchored by an -origin vector
+Individual buttons have an -offset vector relative to the origin
+ ie: the face buttons center is (90, 208) on-screen, and the Triangle button is 12 units to the left, and 18 units above
+ try (set-vector! stick-origin 60 20 0 0)
+
+Use L2 + L3 + Dpad to start editing the input display
+The dpad press displays text and changes the edit mode
+Down lets you edit buttons, left lets you edit the sticks, right for dpad, up to stop editing
+|#
+
+;; *show-input-display* is the on/off switch controlling whether this is displayed
+
+;
+;Button stuff
+;
+;Button center coordinates
+(define button-origin (new 'static 'vector4w :x 90 :y 208 :z 0 :w 0))
+
+;Face buttons
+(define triangle-offset (new 'static 'vector4w :x -12 :y -18 :z 0 :w 0))
+
+(define x-offset (new 'static 'vector4w :x -12 :y 2 :z 0 :w 0))
+
+(define circle-offset (new 'static 'vector4w :x 6 :y -8 :z 0 :w 0))
+
+(define square-offset (new 'static 'vector4w :x -30 :y -8 :z 0 :w 0))
+
+(defun draw-face-buttons ((arg0 dma-buffer))
+ ;Face buttons. Each button needed added to text-h.gc and english texts
+ (when (cpad-hold? 0 triangle)
+ (draw-string-xy (lookup-text! *common-text* (text-id pad-triangle) #f)
+ arg0
+ (+ (-> button-origin x) (-> triangle-offset x))
+ (+ (-> button-origin y) (-> triangle-offset y))
+ (font-color green)
+ (font-flags shadow kerning large)))
+ (when (cpad-hold? 0 circle)
+ (draw-string-xy (lookup-text! *common-text* (text-id pad-circle) #f)
+ arg0
+ (+ (-> button-origin x) (-> circle-offset x))
+ (+ (-> button-origin y) (-> circle-offset y))
+ (font-color green)
+ (font-flags shadow kerning large)))
+ (when (cpad-hold? 0 x)
+ (draw-string-xy (lookup-text! *common-text* (text-id pad-x) #f)
+ arg0
+ (+ (-> button-origin x) (-> x-offset x))
+ (+ (-> button-origin y) (-> x-offset y))
+ (font-color green)
+ (font-flags shadow kerning large)))
+ (when (cpad-hold? 0 square)
+ (draw-string-xy (lookup-text! *common-text* (text-id pad-square) #f)
+ arg0
+ (+ (-> button-origin x) (-> square-offset x))
+ (+ (-> button-origin y) (-> square-offset y))
+ (font-color green)
+ (font-flags shadow kerning large))))
+
+;
+;Other buttons (LRs, select, start)
+;
+;(define R-x-offset 13)
+;(define L-x-offset (- 28)) ;use a minus for negative numbers
+;(define RL1-y-offset (- 16))
+;(define RL2-y-offset (- 23))
+;(define RL3-y-offset 8)
+
+(define r1-offset (new 'static 'vector4w :x 13 :y -16 :z 0 :w 0))
+
+(define r2-offset (new 'static 'vector4w :x 13 :y -23 :z 0 :w 0))
+
+(define r3-offset (new 'static 'vector4w :x 13 :y 8 :z 0 :w 0))
+
+(define l1-offset (new 'static 'vector4w :x -28 :y -16 :z 0 :w 0))
+
+(define l2-offset (new 'static 'vector4w :x -28 :y -23 :z 0 :w 0))
+
+(define l3-offset (new 'static 'vector4w :x -28 :y 8 :z 0 :w 0))
+
+; select uses l3 offset
+; start uses r3 offset
+
+(defun draw-other-buttons ((arg0 dma-buffer))
+ ;LR/Shoulder buttons
+ (when (cpad-hold? 0 r1)
+ (draw-string-xy "R1"
+ arg0
+ (+ (-> button-origin x) (-> r1-offset x))
+ (+ (-> button-origin y) (-> r1-offset y))
+ (the font-color 13)
+ (font-flags shadow kerning)))
+ (when (cpad-hold? 0 r2)
+ (draw-string-xy "R2"
+ arg0
+ (+ (-> button-origin x) (-> r2-offset x))
+ (+ (-> button-origin y) (-> r2-offset y))
+ (the font-color 13)
+ (font-flags shadow kerning)))
+ (when (cpad-hold? 0 r3)
+ (draw-string-xy "R3"
+ arg0
+ (+ (-> button-origin x) (-> r3-offset x))
+ (+ (-> button-origin y) (-> r3-offset y))
+ (the font-color 13)
+ (font-flags shadow kerning)))
+ (when (cpad-hold? 0 l1)
+ (draw-string-xy "L1"
+ arg0
+ (+ (-> button-origin x) (-> l1-offset x))
+ (+ (-> button-origin y) (-> l1-offset y))
+ (the font-color 13)
+ (font-flags shadow kerning)))
+ (when (cpad-hold? 0 l2)
+ (draw-string-xy "L2"
+ arg0
+ (+ (-> button-origin x) (-> l2-offset x))
+ (+ (-> button-origin y) (-> l2-offset y))
+ (the font-color 13)
+ (font-flags shadow kerning)))
+ (when (cpad-hold? 0 l3)
+ (draw-string-xy "L3"
+ arg0
+ (+ (-> button-origin x) (-> l3-offset x))
+ (+ (-> button-origin y) (-> l3-offset y))
+ (the font-color 13)
+ (font-flags shadow kerning)))
+ ;Select/Start, aligned with L3/R3
+ (when (cpad-hold? 0 start)
+ (draw-string-xy "Start"
+ arg0
+ (+ (-> button-origin x) (-> r3-offset x))
+ (+ (-> button-origin y) (-> r3-offset y))
+ (the font-color 13)
+ (font-flags shadow kerning)))
+ (when (cpad-hold? 0 select)
+ (draw-string-xy "Select"
+ arg0
+ (+ (-> button-origin x) (-> l3-offset x))
+ (+ (-> button-origin y) (-> l3-offset y))
+ (the font-color 13)
+ (font-flags shadow kerning))))
+
+;
+;D-Pad inputs
+;
+(define dpad-origin (new 'static 'vector4w :x 90 :y 208 :z 0 :w 0))
+
+(define up-offset (new 'static 'vector4w :x -12 :y -18 :z 0 :w 0))
+
+(define down-offset (new 'static 'vector4w :x -12 :y 2 :z 0 :w 0))
+
+(define left-offset (new 'static 'vector4w :x -30 :y -8 :z 0 :w 0))
+
+(define right-offset (new 'static 'vector4w :x 6 :y -8 :z 0 :w 0))
+
+(defun draw-dpad ((arg0 dma-buffer))
+ (when (cpad-hold? 0 up)
+ (draw-string-xy "up"
+ arg0
+ (+ (-> dpad-origin x) (-> up-offset x))
+ (+ (-> dpad-origin y) (-> up-offset y))
+ (the font-color 13)
+ (font-flags shadow kerning)))
+ (when (cpad-hold? 0 down)
+ (draw-string-xy "down"
+ arg0
+ (+ (-> dpad-origin x) (-> down-offset x))
+ (+ (-> dpad-origin y) (-> down-offset y))
+ (the font-color 13)
+ (font-flags shadow kerning)))
+ (when (cpad-hold? 0 left)
+ (draw-string-xy "left"
+ arg0
+ (+ (-> dpad-origin x) (-> left-offset x))
+ (+ (-> dpad-origin y) (-> left-offset y))
+ (the font-color 13)
+ (font-flags shadow kerning)))
+ (when (cpad-hold? 0 right)
+ (draw-string-xy "right"
+ arg0
+ (+ (-> dpad-origin x) (-> right-offset x))
+ (+ (-> dpad-origin y) (-> right-offset y))
+ (the font-color 13)
+ (font-flags shadow kerning))))
+
+;
+;Joystick stuff
+;
+(define show-stick1? #t)
+
+(define stick-origin (new 'static 'vector4w :x 30 :y 210 :z 0 :w 0))
+
+(define stick-text-offset (new 'static 'vector4w :x -20 :y -20 :z 0 :w 0))
+
+;no idea how the draw scaling works, this seems good enough
+;length of axes lines, started with 128 and adjusted until it looked good
+(define height-idk (/ 128 5))
+
+(define width-idk (/ 128 3))
+
+(defun draw-stick ((arg0 dma-buffer))
+ (when show-stick1?
+ ;up/down axis
+ (draw-sprite2d-xy arg0
+ (-> stick-origin x) ;the vertical line's x coordinate = stick center x coordinate
+ (- (-> stick-origin y) (/ height-idk 2)) ;we shift the line up, by half its height, so that the line midpoint = center y coordinate
+ 2
+ height-idk
+ *color-black*)
+ ;left/right axis
+ (draw-sprite2d-xy arg0 (- (-> stick-origin x) (/ width-idk 2)) (-> stick-origin y) width-idk 1 *color-gray*)
+ ;Text for stick values
+ (draw-string-xy-scaled (string-format "~d ~d" (-> *cpad-list* cpads 0 leftx) (-> *cpad-list* cpads 0 lefty))
+ arg0
+ (+ (-> stick-origin x) (-> stick-text-offset x))
+ (+ (-> stick-origin y) (-> stick-text-offset y))
+ (font-color green)
+ (font-flags shadow kerning)
+ 0.75)
+ ;raw stick location, nice for locating screen coordinates
+ ;(draw-sprite2d-xy testbuf (the int (-> *cpad-list* cpads 0 leftx)) (the int(-> *cpad-list* cpads 0 lefty)) 5 3 *color-red*)
+ ;stick location scaled to picture
+ (draw-sprite2d-xy arg0
+ ;analog-input takes left stick x int value and maps its min/max to negative/positive of the last argument
+ ;in our case we scale to half of the picture's width, so the stick can go full + (right) or full - (left)
+ (+ (-> stick-origin x)
+ (analog-input (the int (-> *cpad-list* cpads 0 leftx)) 128.0 0.0 128.0 (the float (/ width-idk 2))))
+ ;repeat for y coordinate
+ (+ (-> stick-origin y)
+ (analog-input (the int (-> *cpad-list* cpads 0 lefty)) 128.0 0.0 128.0 (the float (/ height-idk 2))))
+ 3
+ 2
+ *color-cyan*)))
+
+;
+;Second stick
+;
+(define show-stick2? #f)
+
+(define stick2-origin (new 'static 'vector4w :x 150 :y 210 :z 0 :w 0))
+
+(define stick2-text-offset (new 'static 'vector4w :x -20 :y -20 :z 0 :w 0))
+
+(defun draw-stick2 ((arg0 dma-buffer))
+ (when show-stick2?
+ ;up/down axis
+ (draw-sprite2d-xy arg0 (-> stick2-origin x) (- (-> stick2-origin y) (/ height-idk 2)) 2 height-idk *color-black*)
+ ;left/right axis
+ (draw-sprite2d-xy arg0 (- (-> stick2-origin x) (/ width-idk 2)) (-> stick2-origin y) width-idk 1 *color-gray*)
+ ;Text for stick values
+ (draw-string-xy-scaled (string-format "~d ~d" (-> *cpad-list* cpads 0 rightx) (-> *cpad-list* cpads 0 righty))
+ arg0
+ (+ (-> stick2-origin x) (-> stick2-text-offset x))
+ (+ (-> stick2-origin y) (-> stick2-text-offset y))
+ (font-color cyan)
+ (font-flags shadow kerning)
+ 0.75)
+ ;stick location scaled to picture
+ (draw-sprite2d-xy arg0
+ (+ (-> stick2-origin x)
+ (analog-input (the int (-> *cpad-list* cpads 0 rightx)) 128.0 0.0 128.0 (the float (/ width-idk 2))))
+ (+ (-> stick2-origin y)
+ (analog-input (the int (-> *cpad-list* cpads 0 righty)) 128.0 0.0 128.0 (the float (/ height-idk 2))))
+ 3
+ 2
+ *color-green*)))
+
+;
+;edit mode
+;
+(define button-edit-mode 0)
+
+(define button-edit-strings
+ (new 'static 'boxed-array :type string "" "Hold a button and use dpad to move" "Editing Sticks" "Editing Dpad"))
+
+(defun edit-mode-controls ()
+ ;Hold L2 + L3 + dpad to change the edit mode
+ (when (and (cpad-hold? 0 l2) (cpad-hold? 0 l3))
+ (when (cpad-pressed? 0 up)
+ (set! button-edit-mode 0))
+ (when (cpad-pressed? 0 down)
+ (set! button-edit-mode 1))
+ (when (cpad-pressed? 0 left)
+ (set! button-edit-mode 2))
+ (when (cpad-pressed? 0 right)
+ (set! button-edit-mode 3)))
+ ;moves individual face buttons with dpad
+ (when (= button-edit-mode 1)
+ (when (cpad-hold? 0 triangle) ;when you hold triangle, you can move triangle's offset vector with the dpad
+ (when (cpad-hold? 0 up)
+ (+! (-> triangle-offset y) -1))
+ (when (cpad-hold? 0 down)
+ (+! (-> triangle-offset y) 1))
+ (when (cpad-hold? 0 left)
+ (+! (-> triangle-offset x) -1))
+ (when (cpad-hold? 0 right)
+ (+! (-> triangle-offset x) 1)))
+ (when (cpad-hold? 0 circle)
+ (when (cpad-hold? 0 up)
+ (+! (-> circle-offset y) -1))
+ (when (cpad-hold? 0 down)
+ (+! (-> circle-offset y) 1))
+ (when (cpad-hold? 0 left)
+ (+! (-> circle-offset x) -1))
+ (when (cpad-hold? 0 right)
+ (+! (-> circle-offset x) 1)))
+ (when (cpad-hold? 0 x)
+ (when (cpad-hold? 0 up)
+ (+! (-> x-offset y) -1))
+ (when (cpad-hold? 0 down)
+ (+! (-> x-offset y) 1))
+ (when (cpad-hold? 0 left)
+ (+! (-> x-offset x) -1))
+ (when (cpad-hold? 0 right)
+ (+! (-> x-offset x) 1)))
+ (when (cpad-hold? 0 square)
+ (when (cpad-hold? 0 up)
+ (+! (-> square-offset y) -1))
+ (when (cpad-hold? 0 down)
+ (+! (-> square-offset y) 1))
+ (when (cpad-hold? 0 left)
+ (+! (-> square-offset x) -1))
+ (when (cpad-hold? 0 right)
+ (+! (-> square-offset x) 1)))
+ ;LR buttons
+ (when (cpad-hold? 0 r1)
+ (when (cpad-hold? 0 up)
+ (+! (-> r1-offset y) -1))
+ (when (cpad-hold? 0 down)
+ (+! (-> r1-offset y) 1))
+ (when (cpad-hold? 0 left)
+ (+! (-> r1-offset x) -1))
+ (when (cpad-hold? 0 right)
+ (+! (-> r1-offset x) 1)))
+ (when (cpad-hold? 0 r2)
+ (when (cpad-hold? 0 up)
+ (+! (-> r2-offset y) -1))
+ (when (cpad-hold? 0 down)
+ (+! (-> r2-offset y) 1))
+ (when (cpad-hold? 0 left)
+ (+! (-> r2-offset x) -1))
+ (when (cpad-hold? 0 right)
+ (+! (-> r2-offset x) 1)))
+ (when (or (cpad-hold? 0 r3) (cpad-hold? 0 start)) ;use an or here since start/r3 share coords
+ (when (cpad-hold? 0 up)
+ (+! (-> r3-offset y) -1))
+ (when (cpad-hold? 0 down)
+ (+! (-> r3-offset y) 1))
+ (when (cpad-hold? 0 left)
+ (+! (-> r3-offset x) -1))
+ (when (cpad-hold? 0 right)
+ (+! (-> r3-offset x) 1)))
+ (when (cpad-hold? 0 l1)
+ (when (cpad-hold? 0 up)
+ (+! (-> l1-offset y) -1))
+ (when (cpad-hold? 0 down)
+ (+! (-> l1-offset y) 1))
+ (when (cpad-hold? 0 left)
+ (+! (-> l1-offset x) -1))
+ (when (cpad-hold? 0 right)
+ (+! (-> l1-offset x) 1)))
+ (when (cpad-hold? 0 l2)
+ (when (cpad-hold? 0 up)
+ (+! (-> l2-offset y) -1))
+ (when (cpad-hold? 0 down)
+ (+! (-> l2-offset y) 1))
+ (when (cpad-hold? 0 left)
+ (+! (-> l2-offset x) -1))
+ (when (cpad-hold? 0 right)
+ (+! (-> l2-offset x) 1)))
+ (when (or (cpad-hold? 0 l3) (cpad-hold? 0 select)) ;use an or here since select/l3 share coords
+ (when (cpad-hold? 0 up)
+ (+! (-> l3-offset y) -1))
+ (when (cpad-hold? 0 down)
+ (+! (-> l3-offset y) 1))
+ (when (cpad-hold? 0 left)
+ (+! (-> l3-offset x) -1))
+ (when (cpad-hold? 0 right)
+ (+! (-> l3-offset x) 1))))
+ ;moves joysticks
+ (when (= button-edit-mode 2)
+ ;stick1 with dpad
+ (when (cpad-hold? 0 up)
+ (+! (-> stick-origin y) -1))
+ (when (cpad-hold? 0 down)
+ (+! (-> stick-origin y) 1))
+ (when (cpad-hold? 0 left)
+ (+! (-> stick-origin x) -1))
+ (when (cpad-hold? 0 right)
+ (+! (-> stick-origin x) 1))
+ (when (cpad-pressed? 0 l3)
+ (not! show-stick1?))
+ ;stick2 with face buttons
+ (when (cpad-hold? 0 triangle)
+ (+! (-> stick2-origin y) -1))
+ (when (cpad-hold? 0 x)
+ (+! (-> stick2-origin y) 1))
+ (when (cpad-hold? 0 square)
+ (+! (-> stick2-origin x) -1))
+ (when (cpad-hold? 0 circle)
+ (+! (-> stick2-origin x) 1))
+ (when (cpad-pressed? 0 r3)
+ (not! show-stick2?)))
+ ;moves dpad
+ (when (= button-edit-mode 3)
+ ;dpad-origin with dpad
+ (when (cpad-hold? 0 up)
+ (+! (-> dpad-origin y) -1))
+ (when (cpad-hold? 0 down)
+ (+! (-> dpad-origin y) 1))
+ (when (cpad-hold? 0 left)
+ (+! (-> dpad-origin x) -1))
+ (when (cpad-hold? 0 right)
+ (+! (-> dpad-origin x) 1))))
+
+;
+;
+;this is the main function/process
+;
+;
+
+(defun input-display-on ()
+ (when (not (process-by-name 'button-proc *active-pool*))
+ ;process-spawn-function, spawns a process that runs the function you give it
+ (process-spawn-function process
+ :name 'button-proc
+ ;This lambda is our function
+ (lambda :behavior process ()
+ ;Code before the loop runs once on process spawn
+ ;
+ ;Loop runs once per frame while process is active
+ (loop
+ ;function that checks input to turn on editing
+ (edit-mode-controls)
+ ;Start a bucket thing block so we can use draw functions
+ (with-dma-buffer-add-bucket ((testbuf (-> (current-frame) global-buf)) (bucket-id debug-no-zbuf))
+ ;Edit mode string draws
+ (when (and (cpad-hold? 0 l2) (cpad-hold? 0 l3))
+ (draw-string-xy-scaled "L2+L3+Dpad to start editing input display"
+ testbuf
+ 5
+ 170
+ (the font-color 12)
+ (font-flags shadow kerning)
+ 0.7))
+ (when (!= button-edit-mode 0)
+ (draw-string-xy-scaled (string-format "~s" (-> button-edit-strings button-edit-mode))
+ testbuf
+ 5
+ 177
+ (the font-color 12)
+ (font-flags shadow kerning)
+ 0.7))
+ ;Other draw stuff
+ (draw-face-buttons testbuf)
+ (draw-other-buttons testbuf)
+ (draw-dpad testbuf)
+ (draw-stick testbuf)
+ (draw-stick2 testbuf))
+ ;Processes should suspend themselves, the loop will resume next frame
+ (suspend)))))
+ ;Lisp returns the last form as the function return
+ (none))
+
+(defun input-display-off ()
+ "Kill the button process"
+ (when (process-by-name 'button-proc *active-pool*)
+ (kill-by-name 'button-proc *active-pool*))
+ (none))
diff --git a/goal_src/jak1/engine/mods/mod-common-functions.gc b/goal_src/jak1/engine/mods/mod-common-functions.gc
new file mode 100644
index 0000000000..fb250f9667
--- /dev/null
+++ b/goal_src/jak1/engine/mods/mod-common-functions.gc
@@ -0,0 +1,398 @@
+;;-*-Lisp-*-
+(in-package goal)
+
+;; name: mod-common-functions.gc
+;; name in dgo: mod-common-functions
+;; dgos: TODO
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; What is this file for.
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+#|
+ This file is a place where you can define custom functions and GOAL code
+ to call inside of mod-custom-code.gc for example I have defined a function that increases
+ the powercell count by one when it is called
+|#
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Useful GOAL modding documentation
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+#|
+Checks the condition and if it is true it does first argument if false it does optional second argument
+(if (condition) (do if true) (do if false))
+
+Gives a random FLOAT or INT between the provided ranges when called
+(rand-vu-float-range 0.0 2.0)
+(rand-vu-int-range 0 10)
+
+if the result of rand-vu-int-range is 1, then DANCE! if it is not 1, then Don't dance
+(if (= (rand-vu-int-range 0 10) 1) (DANCE!) (Don't dance))
+|#
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Define Custom Variables to use in mods
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Define Custom Functions to call in mods
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; add your own unique custom functions here!
+
+;; Macros can be used more-or-less just like functions
+(defmacro current-cell-count ()
+ `(-> *game-info* fuel))
+
+(defmacro set-current-cell-count (count)
+ `(set! (-> *game-info* fuel) ,count))
+
+(defun increase-power-cell-by-one ()
+ (set-current-cell-count (+ (current-cell-count) 1))
+ ;; with the two macros defined above, this is equivalent to
+ ;; (set! (-> *game-info* fuel) (+ (-> *game-info* fuel) 1))
+ (none))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Define Approved Custom Functions/Macros to call in all mods
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; These are included with the mod base and you are welcome to use them in your mods!
+
+(defmacro current-checkpoint-name ()
+ `(-> *game-info* current-continue name))
+
+(defun set-current-checkpoint-by-name ((name string))
+ (set-continue! *game-info* name))
+
+(defmacro current-level-name ()
+ `(-> (level-get-target-inside *level*) name))
+
+(defmacro current-orb-count ()
+ `(-> *game-info* money))
+
+(defmacro current-cutscene ()
+ `(-> *art-control* active-stream))
+
+(defmacro set-cpad-pressed! (pad-idx &rest buttons)
+ `(logior! (cpad-pressed ,pad-idx) (pad-buttons ,@buttons)))
+
+(defmacro set-cpad-hold! (pad-idx &rest buttons)
+ `(logior! (cpad-hold ,pad-idx) (pad-buttons ,@buttons)))
+
+;;This function moves an actor to the given coordinates
+;;example: (move-actor "farmer-3" 3.0 74.0 -120.0)
+(defun move-actor ((actor-name string) (x float) (y float) (z float))
+ (when (entity-by-name actor-name)
+ (let* ((entity-actor (entity-by-name actor-name))
+ (actor (-> entity-actor extra process)))
+ (when actor
+ (case (-> actor type)
+ ((fuel-cell)
+ (set-vector! (-> entity-actor trans) (meters x) (meters y) (meters z) 1.0)
+ (set-vector! (-> entity-actor extra trans) (meters x) (meters y) (meters z) 1.0)
+ (set-vector! (-> (the process-drawable actor) root trans) (meters x) (meters y) (meters z) 1.0)
+ (set-vector! (-> (the fuel-cell actor) base) (meters x) (meters y) (meters z) 1.0)
+ (set-vector! (-> (the fuel-cell actor) root trans) (meters x) (meters y) (meters z) 1.0)
+ (when (name= (-> (the fuel-cell actor) state name) 'wait)
+ ;; only move collision when idle (messes up glowing in cutscene)
+ (set! (-> (the fuel-cell actor) root root-prim world-sphere x) (meters x))
+ (set! (-> (the fuel-cell actor) root root-prim world-sphere y) (meters y))
+ (set! (-> (the fuel-cell actor) root root-prim world-sphere z) (meters z))))
+ ((orb-cache-top)
+ ;; don't move while its activated (let it go up/down)
+ (when (not (name= (-> (the orb-cache-top actor) state name) 'orb-cache-top-activate))
+ (set-vector! (-> entity-actor trans) (meters x) (meters y) (meters z) 1.0)
+ (set-vector! (-> entity-actor extra trans) (meters x) (meters y) (meters z) 1.0)
+ (set-vector! (-> (the process-drawable actor) root trans) (meters x) (meters y) (meters z) 1.0)
+ (set-vector! (-> (the orb-cache-top actor) basetrans) (meters x) (meters y) (meters z) 1.0)
+ (set-vector! (-> (the orb-cache-top actor) root trans) (meters x) (meters y) (meters z) 1.0)
+ (set! (-> (the orb-cache-top actor) root root-prim world-sphere x) (meters x))
+ (set! (-> (the orb-cache-top actor) root root-prim world-sphere y) (meters y))
+ (set! (-> (the orb-cache-top actor) root root-prim world-sphere z) (meters z))
+ (set-vector! (-> (the orb-cache-top actor) draw origin) (meters x) (meters y) (meters z) 1.0)
+ (let ((radius (-> (the process-drawable actor) draw radius))
+ (bounds (res-lump-data entity-actor 'visvol (inline-array vector))))
+ (set-vector! (-> bounds 0) (- (meters x) radius) (meters y) (- (meters z) radius) 1.0)
+ (set-vector! (-> bounds 1) (+ (meters x) radius) (meters y) (+ (meters z) radius) 1.0))))
+ ((money)
+ ;; don't move orbs if being blue-eco-sucked
+ (when (not (logtest? (-> (the money actor) flags) (collectable-flags suck)))
+ (set-vector! (-> entity-actor trans) (meters x) (meters y) (meters z) 1.0)
+ (set-vector! (-> entity-actor extra trans) (meters x) (meters y) (meters z) 1.0)
+ (set-vector! (-> (the process-drawable actor) root trans) (meters x) (meters y) (meters z) 1.0)
+ (set-vector! (-> (the money actor) base) (meters x) (meters y) (meters z) 1.0)
+ (set-vector! (-> (the money actor) root trans) (meters x) (meters y) (meters z) 1.0)
+ (set! (-> (the money actor) root root-prim world-sphere x) (meters x))
+ (set! (-> (the money actor) root root-prim world-sphere y) (meters y))
+ (set! (-> (the money actor) root root-prim world-sphere z) (meters z))))
+ ((crate crate-buzzer)
+ ;; only move crates if they're not jumping
+ (when (= (-> (the crate actor) smush amp) 0.0)
+ (set-vector! (-> entity-actor trans) (meters x) (meters y) (meters z) 1.0)
+ (set-vector! (-> entity-actor extra trans) (meters x) (meters y) (meters z) 1.0)
+ (set-vector! (-> (the process-drawable actor) root trans) (meters x) (meters y) (meters z) 1.0)
+ (set-vector! (-> (the crate actor) base) (meters x) (meters y) (meters z) 1.0)
+ ;; (set-vector! (-> (the crate actor) root trans) (meters x) (meters y) (meters z) 1.0)
+ ;; (set! (-> (the crate actor) root root-prim world-sphere x) (meters x))
+ ;; (set! (-> (the crate actor) root root-prim world-sphere y) (meters y))
+ ;; (set! (-> (the crate actor) root root-prim world-sphere z) (meters z))
+ ))
+ ((darkvine)
+ (set-vector! (-> entity-actor trans) (meters x) (meters y) (meters z) 1.0)
+ (set-vector! (-> entity-actor extra trans) (meters x) (meters y) (meters z) 1.0)
+ (set-vector! (-> (the process-drawable actor) root trans) (meters x) (meters y) (meters z) 1.0))
+ (else
+ (format 0 "unexpected actor type ~S ~S ~S~%" actor-name (-> entity-actor type) (-> actor type))
+ (set-vector! (-> entity-actor trans) (meters x) (meters y) (meters z) 1.0)
+ (set-vector! (-> entity-actor extra trans) (meters x) (meters y) (meters z) 1.0)
+ (set-vector! (-> (the process-drawable actor) root trans) (meters x) (meters y) (meters z) 1.0))))))
+ (none))
+
+(defun process-by-aid ((arg0 uint))
+ "Get the process for the entity with the given aid. If there is no entity or process, #f."
+ (let ((v1-0 (entity-by-aid arg0))) (if v1-0 (-> v1-0 extra process))))
+
+(defun spawn-actor-by-name ((name string))
+ ;; Takes in the string of name of a actor, and spawns a new process based on the entity.
+ (let* ((entity-actor (the entity-actor (entity-by-name name)))
+ (type (-> entity-actor etype))
+ (e-info (entity-info-lookup type)))
+ (when (entity-by-name name)
+ (init-entity (get-process *default-dead-pool* type (if e-info (-> e-info heap-size) #x4000)) entity-actor)
+ (sound-play "buzzer-pickup"))
+ (if (not (entity-by-name name)) (sound-play "oof")))
+ (none))
+
+;;Draws a debug sphere on the actor, takes in a string actor name and a radius for the sphere in meters
+(defun draw-debug-sphere-on-actor ((actorName string) (radius float))
+ (when *debug-segment*
+ (when (process-by-ename actorName)
+ (add-debug-sphere #t
+ (bucket-id debug)
+ (-> (the-as process-drawable (process-by-ename actorName)) root trans)
+ (meters radius)
+ (static-rgba 0 #xff 0 #x40))))
+ (none))
+
+;;This function moves a given actor to jaks current position, then prints a (move-actors) call in gk.exe
+(defun move-to-jak ((arg0 string))
+ (format #t
+ "move-actor code: (move-actor ~a ~m ~m ~m)~%"
+ arg0
+ (-> (target-pos 0) x)
+ (-> (target-pos 0) y)
+ (-> (target-pos 0) z))
+ (when (process-by-ename arg0)
+ (set-vector! (-> (-> (the process-drawable (process-by-ename arg0)) root) trans)
+ (-> (target-pos 0) x)
+ (-> (target-pos 0) y)
+ (-> (target-pos 0) z)
+ 1.0)
+ (if (type-type? (-> (process-by-ename arg0) type) crate)
+ (begin
+ (set! (-> (the crate (process-by-ename arg0)) base y) (-> (target-pos 0) y)))
+ (none))
+ (if (type-type? (-> (process-by-ename arg0) type) money)
+ (begin
+ (set! (-> (the money (process-by-ename arg0)) base y) (-> (target-pos 0) y)))
+ (none))
+ (if (type-type? (-> (process-by-ename arg0) type) fuel-cell)
+ (begin
+ (set! (-> (the fuel-cell (process-by-ename arg0)) base y) (-> (target-pos 0) y)))
+ (none))))
+
+;; quick macro for setting vector xyz in meters, leaving w alone
+(defmacro set-vector-meters! (dst x y z)
+ `(set-vector! ,dst (meters ,x) (meters ,y) (meters ,z) (-> ,dst w)))
+
+;; quick macro for constructing static vector with w=1
+(defmacro static-vector-meters (x y z)
+ `(new 'static 'vector :x (meters ,x) :y (meters ,y) :z (meters ,z) :w 1.0))
+
+;; prints vector xyz in meters
+(defmacro print-vector-meters (vec &key (dst #t))
+ `(format ,dst "~m ~m ~m~%" (-> ,vec x) (-> ,vec y) (-> ,vec z)))
+
+;; takes a path-control and xyz values to offsets every node in the path by
+(defmacro shift-path! (path x y z)
+ `(let ((voff (static-vector-meters ,x ,y ,z)))
+ (dotimes (idx (-> ,path num-cverts))
+ (vector+! (-> ,path cverts idx) (-> ,path cverts idx) voff))))
+
+;; prints all the nodes in a path in meters
+(defmacro path-print-meters (path)
+ `(dotimes (idx (-> ,path num-cverts))
+ (print-vector-meters (-> ,path cverts idx))))
+
+;; prints the position (root trans) of a process-drawable
+(defmacro pd-pos-m (procname)
+ `(let* ((obj (the process-drawable (process-by-ename ,procname)))
+ (vec (-> obj root trans)))
+ (format 0 "~m ~m ~m~%" (-> vec x) (-> vec y) (-> vec z))
+ (none)))
+
+;;This function moves an actor based on jaks position + an offset
+(defun move-to-behind-jak ((arg0 string) (arg1 meters) (arg2 meters))
+ (when (process-by-ename arg0)
+ (set-vector! (-> (-> (the process-drawable (process-by-ename arg0)) root) trans)
+ (- (-> (target-pos 0) x) (meters arg1))
+ (+ (-> (target-pos 0) y) (meters arg2))
+ (- (-> (target-pos 0) z) (meters arg1))
+ 1.0)
+ (if (type-type? (-> (process-by-ename arg0) type) money)
+ (begin
+ (set! (-> (the money (process-by-ename arg0)) base y) (-> (target-pos 0) y)))
+ (none))
+ (if (type-type? (-> (process-by-ename arg0) type) fuel-cell)
+ (begin
+ (set! (-> (the fuel-cell (process-by-ename arg0)) base y) (-> (target-pos 0) y)))
+ (none))))
+
+;;This turns on play hints
+(defun turnonplayhints ()
+ (set! (-> *setting-control* default play-hints) #t))
+
+;;This turns off playhints
+(defun turnoffplayhints ()
+ (set! (-> *setting-control* default play-hints) #f))
+
+;;This turns on collision render when called
+(defun turnonCollisionmode ()
+ (set! *collision-renderer* #t)
+ (logclear! *vu1-enable-user-menu* (vu1-renderer-mask tfrag trans-tfrag tie tie-near)))
+
+;;This turns off collision render when called
+(defun turnoffCollisionmode ()
+ (set! *collision-renderer* #f)
+ (logior! *vu1-enable-user-menu* (vu1-renderer-mask tfrag trans-tfrag tie tie-near)))
+
+;;This makes it thunder in the current level
+(defun thunderTime ()
+ (set! (-> (level-get-target-inside *level*) mood-func) update-mood-village2)
+ (set! (-> (level-get-target-inside *level*) mood) *village2-mood*))
+
+;;This makes the current level dark when called
+(defun DarkesetGlitchTime ()
+ (set! (-> (level-get-target-inside *level*) mood-func) update-mood-finalboss)
+ (set! (-> (level-get-target-inside *level*) mood) *finalboss-mood*))
+
+;;This needs fixed
+(defun rainyTime ()
+ (set! (-> (level-get-target-inside *level*) mood-func) update-mood-swamp)
+ (set! (-> (level-get-target-inside *level*) mood) *swamp-mood*))
+
+;;This needs fixed
+(defun snowingTime ()
+ (set! (-> (level-get-target-inside *level*) mood-func) update-mood-snow)
+ (set! (-> (level-get-target-inside *level*) mood) *snow-mood*))
+
+;;This makes the current levels weather the same as village1
+(defun defaultWeatherTime ()
+ (set! (-> (level-get-target-inside *level*) mood-func) update-mood-village1)
+ (set! (-> (level-get-target-inside *level*) mood) *village1-mood*))
+
+;;This moves jak to a provided coordinate example call
+;;(tp-jak 0.0 12.0 32.32)
+(defun tp-jak ((arg0 float) (arg1 float) (arg2 float))
+ (set! (-> (target-pos 0) x) (meters arg0))
+ (set! (-> (target-pos 0) y) (meters arg1))
+ (set! (-> (target-pos 0) z) (meters arg2)))
+
+;;This returns true or false depending on if jak is within a provided distance from an actor
+(defun close? ((actor-ename string) (dist float))
+ (and (process-by-ename actor-ename)
+ (<= (vector-vector-distance (target-pos 0) (-> (the process-drawable (process-by-ename actor-ename)) root trans)) dist)))
+
+;; This returns true or false if jak is within a bubble defined by coordinates and width
+(defun in-bubble? ((x float) (y float) (z float) (w float))
+ (<= (vector-vector-distance (target-pos 0) (set-vector! (new-stack-vector0) x y z 1.0)) (/ w 2.0)))
+
+(defun music-manager ()
+ (stop-main-music)
+ (case (-> (level-get-target-inside *level*) name)
+ (('test-zone)
+ ;; (if (> (knuth-rand-int-range 0 15) (+ 8 5))
+ ;; (play-main-music "SND/music-test-zone.mp3" (the int (-> *setting-control* default music-volume)))
+ ;; (play-main-music "SND/music-test-zone-track2.mp3" (the int (-> *setting-control* default music-volume))))
+ )
+ ;;Add more cases here for each level
+ (else
+ (play-main-music "" (the int (-> *setting-control* default music-volume)))
+ ;;(stop-main-music) This function is broken but playing a invalid sound does the same thing
+ ))
+ (none))
+
+(defbehavior music-manager-proc process ()
+ (music-manager)
+ (none))
+
+(define *pc-temp-vector* (new 'static 'vector :x 1.0 :y 1.0 :z 1.0))
+
+(defun get-joint-pos-by-name ((actor process-drawable) (name string))
+ (when actor
+ (dotimes (i (-> actor node-list length))
+ (when (string= (-> actor node-list data i joint name) name)
+ (return (the-as vector (vector<-cspace! *pc-temp-vector* (-> actor node-list data i))))
+ (the-as vector (new-stack-vector0))))
+ (the-as vector (new-stack-vector0))))
+
+(defun print-joint-names ((actor process-drawable))
+ (format #t (string-format "Actor: ~S has these ~D Joints:~%" (-> actor name) (-> actor node-list length)))
+ (dotimes (i (-> actor node-list length))
+ (format #t (string-format "~D. ~S~%" i (-> actor node-list data i joint name))))
+ (none))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Jak Color functions
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun draw-xyz ((jak target) (x float) (y float) (z float))
+ (set! (-> jak draw color-mult x) x)
+ (set! (-> jak draw color-mult y) y)
+ (set! (-> jak draw color-mult z) z))
+
+(defun draw-normal ((jak target))
+ (draw-xyz jak 1.0 1.0 1.0))
+
+(defun draw-white ((jak target))
+ (draw-xyz jak 10.0 10.0 10.0))
+
+(defun draw-black ((jak target))
+ (draw-xyz jak 0.0 0.0 0.0))
+
+(defun draw-red ((jak target))
+ (draw-xyz jak 3.0 0.0 0.0))
+
+(defun draw-green ((jak target))
+ (draw-xyz jak 0.0 3.0 0.0))
+
+(defun draw-blue ((jak target))
+ (draw-xyz jak 0.0 0.0 3.0))
+
+(defun draw-yellow ((jak target))
+ (draw-xyz jak 3.0 3.0 0.0))
+
+(defun draw-pink ((jak target))
+ (draw-xyz jak 3.0 0.0 3.0))
+
+(defun draw-light-blue ((jak target))
+ (draw-xyz jak 0.0 3.0 3.0))
+
+;; Helper functions for spawning orbs (used by orb placer in debug mode)
+
+(defun spawn-money ((vec vector) (amount float) (bob? symbol))
+ (let ((fax (new 'static 'fact-info)))
+ (set! (-> fax pickup-type) (pickup-type money))
+ (set! (-> fax pickup-amount) amount)
+ (set! (-> fax pickup-spawn-amount) amount)
+ (set! (-> fax fade-time) (the-as time-frame 0))
+ (let ((proc (the money (ppointer->process (birth-pickup-at-point vec (pickup-type money) amount #t *active-pool* fax)))))
+ (when bob?
+ (set! (-> proc bob-amount) 1024.0))
+ (format 0 "spawned ~A~%" proc)
+ ;; return handle to the orb
+ (process->handle proc))))
+
+(defun spawn-money-meters ((x float) (y float) (z float) (amount float) (bob? symbol))
+ (let ((vec (new 'stack-no-clear 'vector))) (set-vector-meters! vec x y z) (spawn-money vec amount bob?)))
diff --git a/goal_src/jak1/engine/mods/mod-custom-code.gc b/goal_src/jak1/engine/mods/mod-custom-code.gc
new file mode 100644
index 0000000000..71d631c764
--- /dev/null
+++ b/goal_src/jak1/engine/mods/mod-custom-code.gc
@@ -0,0 +1,101 @@
+;;-*-Lisp-*-
+(in-package goal)
+
+;; name: mod-custom-code.gc
+;; name in dgo: mod-custom-code
+;; dgos: TODO
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; What is this file for.
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+#|
+ This file contains function defenitions that are pre placed in the mod base,
+ so if you place custom code inside of these functions, it will exectue based on
+ the name of the function, for example, if you place (set! (-> *game-info* fuel) (+ (-> *game-info* fuel) 1))
+ to the function named runs-on-orb-pickup, then jaks powercell count will increase each time you collect
+ an orb
+|#
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Begin function defintions.
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defun runs-every-frame ()
+ ;;(increase-power-cell-by-one) This is a call to increase-power-cell-by-one defined in mod-common-functions.gc
+ (if *show-input-display* (input-display-on) (input-display-off))
+ ;; ensure orb-placer is spawned/killed as requested, debug menu is updated
+ (when *debug-segment*
+ (orb-placer-maintenance))
+ (none))
+
+(defun runs-on-orb-pickup ((parent process-tree))
+ (let* ((from-cache? (and parent (type-type? (-> parent type) orb-cache-top))))
+ ;; Code here runs on ANY orb pickup
+ (when from-cache?
+ ;; Code here runs only if the orb was from an orb cache
+ )
+ (when (not from-cache?)
+ ;; Code here runs only if the orb was NOT from an orb cache
+ ))
+ (none))
+
+(defun runs-on-fly-pickup ()
+ ;; Code here runs on any scout fly pickup
+ (none))
+
+(defun runs-on-cell-pickup ((cell-event symbol))
+ (case cell-event
+ (('pickup)
+ ;; Code here runs as soon as you pickup a powercell
+ )
+ (('cutscene-end)
+ ;; Code here runs at the end of any powercell cutscene
+ ))
+ (none))
+
+(defun runs-on-eco-pickup ((eco-type pickup-type) (parent process-tree))
+ (let* ((from-vent? (and parent (type-type? (-> parent type) vent))))
+ ;; Code here runs as soon as you pickup ANY eco
+ (case eco-type
+ (((pickup-type eco-yellow))
+ ;; Code here runs as soon as you pickup yellow eco
+ )
+ (((pickup-type eco-red))
+ ;; Code here runs as soon as you pickup red eco
+ )
+ (((pickup-type eco-blue))
+ ;; Code here runs as soon as you pickup blue eco
+ )
+ (((pickup-type eco-pill))
+ ;; Code here runs as soon as you pickup small green eco
+ )
+ (((pickup-type eco-green))
+ ;; Code here runs as soon as you pickup big green eco
+ ))
+ (when from-vent?
+ ;; Code here runs only if the eco was picked up from a vent
+ ))
+ (none))
+
+(defun runs-on-jak-spawn ()
+ ;; Code here runs every time jak spawns (loading a file new game or death)
+ ;;uncomment this to use custom music for custom levels - the function is in mod-common-functions.gc
+ ;;(process-spawn-function process music-manager-proc)
+ (none))
+
+(defun runs-on-jak-death ((death-event symbol))
+ (case death-event
+ (('dying)
+ ;; Code here runs immediately every time jak dies, before any death animation or death cutscene
+ )
+ (('blackout)
+ ;; Code here runs after jak dies (and any death cutscene finishes), during the blackout before he spawns
+ ))
+ (none))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; deprecated function defintions.
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+#| these are no longer recommended/supported however we include them anyways to not break anyones mods. |#
diff --git a/goal_src/jak1/engine/mods/mod-debug.gc b/goal_src/jak1/engine/mods/mod-debug.gc
new file mode 100644
index 0000000000..4c62a74b47
--- /dev/null
+++ b/goal_src/jak1/engine/mods/mod-debug.gc
@@ -0,0 +1,27 @@
+;;-*-Lisp-*-
+(in-package goal)
+
+;; For debug-only additions to the mod-base. Anything defined in this file will be unavailable in retail mode.
+(declare-file (debug))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Additional debug menu(s)
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defun-debug debug-menu-make-modding-tools-menu ((ctx debug-menu-context))
+ (let ((modding-tools-menu (new 'debug 'debug-menu ctx "Modding Tools")))
+ ;; orb-placer menu
+ (let ((orb-placer-menu (new 'debug 'debug-menu ctx "Orb Placer")))
+ (debug-menu-append-item orb-placer-menu (new-dm-bool "Edit Mode?" *orb-placer-enabled?* dm-boolean-toggle-pick-func))
+ (debug-menu-append-item orb-placer-menu (new-dm-func "Add Orb" #f orb-placer-add))
+ (let ((select-orb-menu (new 'debug 'debug-menu ctx "Select Orb")))
+ (set! *orb-placer-select-menu* select-orb-menu)
+ ;; populated on orb add
+ (debug-menu-append-item orb-placer-menu (new-dm-submenu "Select Orb" select-orb-menu)))
+ (debug-menu-append-item orb-placer-menu (new-dm-func "Print Selected Orb Position" #f orb-placer-print-selected))
+ (debug-menu-append-item orb-placer-menu (new-dm-func "Print All Orb Positions" #f orb-placer-print-all))
+ (debug-menu-append-item modding-tools-menu (new-dm-submenu "Orb Placer" orb-placer-menu)))
+ (new-dm-submenu "Modding Tools" modding-tools-menu)))
+
+(when (-> *debug-menu-context* root-menu)
+ (debug-menu-append-item (-> *debug-menu-context* root-menu) (debug-menu-make-modding-tools-menu *debug-menu-context*)))
diff --git a/goal_src/jak1/engine/mods/mod-settings.gc b/goal_src/jak1/engine/mods/mod-settings.gc
new file mode 100644
index 0000000000..ecb8dec835
--- /dev/null
+++ b/goal_src/jak1/engine/mods/mod-settings.gc
@@ -0,0 +1,55 @@
+;;-*-Lisp-*-
+(in-package goal)
+
+;; name: mod-common-functions.gc
+;; name in dgo: mod-common-functions
+;; dgos: TODO
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; What is this file for.
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+#|
+ This file is a place where you can define custom functions and GOAL code
+ to call inside of mod-custom-code.gc for example I have defined a function that increases
+ the powercell count by one when it is called
+|#
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Useful GOAL modding documentation
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+#|
+Checks the condition and if it is true it does first argument if false it does optional second argument
+(if (condition) (do if true) (do if false))
+
+Gives a random FLOAT or INT between the provided ranges when called
+(rand-vu-float-range 0.0 2.0)
+(rand-vu-int-range 0 10)
+
+if the result of rand-vu-int-range is 1, then DANCE! if it is not 1, then Don't dance
+(if (= (rand-vu-int-range 0 10) 1) (DANCE!) (Don't dance))
+|#
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Define Settings to use in mods
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(define startingDebugContinuePoint "village1-hut")
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Define Custom Settings Variables to use in mods
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; Change #f to #t here to remove flutflut's invisible walls and prevent deload crashes
+(define *allow-flutflut-anywhere* #f)
+
+;; Change #f to #t here to remove zoomer's invisible walls and prevent deload crashes
+(define *allow-zoomer-anywhere* #f)
+
+;; Change #f to #t here to show the input display by default
+(define *show-input-display* #f)
+
+;; do NOT change %MODVERSIONPLACEHOLDER% below, otherwise the mod-bundling-tools
+;; will be unable to automatically add version info to the speedrun display
+(define *mod-version-text* "%MODVERSIONPLACEHOLDER%")
diff --git a/goal_src/jak1/engine/mods/orb-placer.gc b/goal_src/jak1/engine/mods/orb-placer.gc
new file mode 100644
index 0000000000..32649efbb3
--- /dev/null
+++ b/goal_src/jak1/engine/mods/orb-placer.gc
@@ -0,0 +1,301 @@
+(in-package goal)
+(declare-file (debug))
+
+;; controls whether the orb-placer process will run
+(define *orb-placer-enabled?* #f)
+
+;; number of orbs orb-placer has placed
+(define *orb-placer-count* 0)
+
+;; max number of orbs
+(defconstant ORB_PLACER_MAX 100)
+
+;; index of currently selected orb for placing
+(define *orb-placer-selected-idx* -1)
+
+;; array of handles to orbs that have been spawned by orb-placer
+(define *orb-placer-orbs* (new 'global 'boxed-array handle ORB_PLACER_MAX))
+
+(define *orb-placer-temp-strs*
+ (new 'static
+ 'boxed-array
+ :type
+ string
+ :length
+ ORB_PLACER_MAX
+ "orba-00"
+ "orba-01"
+ "orba-02"
+ "orba-03"
+ "orba-04"
+ "orba-05"
+ "orba-06"
+ "orba-07"
+ "orba-08"
+ "orba-09"
+ "orba-10"
+ "orba-11"
+ "orba-12"
+ "orba-13"
+ "orba-14"
+ "orba-15"
+ "orba-16"
+ "orba-17"
+ "orba-18"
+ "orba-19"
+ "orba-20"
+ "orba-21"
+ "orba-22"
+ "orba-23"
+ "orba-24"
+ "orba-25"
+ "orba-26"
+ "orba-27"
+ "orba-28"
+ "orba-29"
+ "orba-30"
+ "orba-31"
+ "orba-32"
+ "orba-33"
+ "orba-34"
+ "orba-35"
+ "orba-36"
+ "orba-37"
+ "orba-38"
+ "orba-39"
+ "orba-40"
+ "orba-41"
+ "orba-42"
+ "orba-43"
+ "orba-44"
+ "orba-45"
+ "orba-46"
+ "orba-47"
+ "orba-48"
+ "orba-49"
+ "orba-50"
+ "orba-51"
+ "orba-52"
+ "orba-53"
+ "orba-54"
+ "orba-55"
+ "orba-56"
+ "orba-57"
+ "orba-58"
+ "orba-59"
+ "orba-60"
+ "orba-61"
+ "orba-62"
+ "orba-63"
+ "orba-64"
+ "orba-65"
+ "orba-66"
+ "orba-67"
+ "orba-68"
+ "orba-69"
+ "orba-70"
+ "orba-71"
+ "orba-72"
+ "orba-73"
+ "orba-74"
+ "orba-75"
+ "orba-76"
+ "orba-77"
+ "orba-78"
+ "orba-79"
+ "orba-80"
+ "orba-81"
+ "orba-82"
+ "orba-83"
+ "orba-84"
+ "orba-85"
+ "orba-86"
+ "orba-87"
+ "orba-88"
+ "orba-89"
+ "orba-90"
+ "orba-91"
+ "orba-92"
+ "orba-93"
+ "orba-94"
+ "orba-95"
+ "orba-96"
+ "orba-97"
+ "orba-98"
+ "orba-99"))
+
+;; global for this so we can repopulate it as needed
+(define *orb-placer-select-menu* (the-as debug-menu #f))
+
+(defun dm-orb-placer-select-func ((idx int) (msg debug-menu-msg))
+ (when (= msg (debug-menu-msg press))
+ (cond
+ ((= *orb-placer-selected-idx* idx)
+ ;; deselect
+ (set! *orb-placer-selected-idx* -1)
+ (set! *orb-placer-enabled?* #f))
+ (else
+ ;; select
+ (set! *orb-placer-selected-idx* idx)
+ (set! *orb-placer-enabled?* #t))))
+ (= *orb-placer-selected-idx* idx))
+
+(defun orb-placer-highlight ((orb money) (highlight? symbol))
+ (cond
+ (highlight?
+ (set-vector! (-> orb draw color-mult) 0.8 0.8 0.0 1.0)
+ (set-vector! (-> orb draw color-emissive) 0.0 1.0 0.2 1.0))
+ (else (set-vector! (-> orb draw color-mult) 0.8 0.8 0.8 1.0) (set-vector! (-> orb draw color-emissive) 0.2 0.2 0.2 1.0)))
+ (none))
+
+(defun orb-placer-list-maintenace ((update-debug-list? symbol))
+ (when update-debug-list?
+ (debug-menu-remove-all-items *orb-placer-select-menu*))
+ (dotimes (i *orb-placer-count*)
+ (let ((orb-handle (-> *orb-placer-orbs* i))
+ (is-selected? (and *orb-placer-enabled?* (= i *orb-placer-selected-idx*))))
+ (when (and (nonzero? orb-handle) (handle->process orb-handle))
+ (let ((orb-proc (the money (handle->process orb-handle))))
+ (when (and orb-proc (!= (-> orb-proc next-state name) 'dead-state) (!= (-> orb-proc next-state name) 'hud-collecting))
+ ;; ensure correct highlighting
+ (orb-placer-highlight orb-proc is-selected?)
+ ;; draw z-debug text
+ (add-debug-text-3d #t
+ (bucket-id debug-no-zbuf)
+ (-> *orb-placer-temp-strs* i)
+ (-> orb-proc base)
+ (if is-selected? (font-color red) (font-color white))
+ (new 'static 'vector2h :y 16))
+ (when update-debug-list?
+ ;; append to debug menu list
+ (let ((orb-menu-item (new-dm-flag (-> *orb-placer-temp-strs* i) i dm-orb-placer-select-func)))
+ (debug-menu-append-item *orb-placer-select-menu* orb-menu-item)
+ (when is-selected?
+ (set! (-> *orb-placer-select-menu* selected-item) orb-menu-item)))))))))
+ (when update-debug-list?
+ (set! (-> *orb-placer-select-menu* items) (sort (-> *orb-placer-select-menu* items) debug-menu-node)))
+ (none))
+
+(defun orb-placer-add ()
+ (when (< *orb-placer-count* ORB_PLACER_MAX)
+ (let ((vec (new 'stack-no-clear 'vector))
+ (camera-quat (new-stack-quaternion0))
+ (camera-z-vector (new-stack-vector0)))
+ ;; figure out spawn position
+ (cond
+ (*target*
+ ;; jak exists, use his position
+ (vector-copy! vec (-> *target* root trans))
+ (+! (-> vec y) (meters 2.0)) ;; dont spawn in ground
+ )
+ (else
+ ;; use camera position
+ (vector-copy! vec (-> *math-camera* trans))))
+ ;; convert the camera angle from a matrix to a quaternion (???)
+ (matrix->quaternion camera-quat (-> *camera-combiner* inv-camera-rot))
+ ;; convert the quaternion to a vector representing rotation around z axis (isnt it the y axis in GOAL tho?)
+ (vector-z-quaternion! camera-z-vector camera-quat)
+ ;; shift orb's position with camera angle, by 3m
+ (vector+! vec vec (vector-normalize! camera-z-vector (meters 3.0)))
+ ;; spawn and update orb-placer data
+ (let ((orb-handle (spawn-money vec 1.0 #t)))
+ (when (nonzero? orb-handle)
+ (set! (-> *orb-placer-orbs* *orb-placer-count*) orb-handle)
+ (set! *orb-placer-selected-idx* *orb-placer-count*)
+ (orb-placer-highlight (the money (handle->process orb-handle)) #t)
+ (+! *orb-placer-count* 1)
+ (orb-placer-list-maintenace #t)))))
+ (none))
+
+(defun orb-placer-print-selected ()
+ (when (and (>= *orb-placer-selected-idx* 0) (nonzero? (-> *orb-placer-orbs* *orb-placer-selected-idx*)))
+ (let ((orb (the money (handle->process (-> *orb-placer-orbs* *orb-placer-selected-idx*)))))
+ (when orb
+ (format 0 " ~m, ~m, ~m~%" (-> orb base x) (-> orb base y) (-> orb base z)))))
+ (none))
+
+(defun orb-placer-print-all ()
+ (format 0 "|------------orba start------------|~%")
+ (dotimes (i *orb-placer-count*)
+ (let ((orb-handle (-> *orb-placer-orbs* i)))
+ (when (and (nonzero? orb-handle) (handle->process orb-handle))
+ (let ((orb (the money (handle->process orb-handle))))
+ (format 0 " ~m, ~m, ~m~%" (-> orb base x) (-> orb base y) (-> orb base z))))))
+ (format 0 "|-------------orba end-------------|~%")
+ (none))
+
+;; called from run-every-frame to ensure orb-placer is spawned/killed
+(define *orb-offset-tmp-vec* (new 'global 'vector))
+
+(defun orb-placer-maintenance ()
+ (when *debug-segment*
+ (orb-placer-list-maintenace #f)
+ (when (and (not (paused?)) *orb-placer-enabled?*)
+ (with-dma-buffer-add-bucket ((buf (-> (current-frame) global-buf)) (bucket-id subtitle))
+ (draw-string-xy "Orb Placer Edit Mode" buf 510 2 (font-color red) (font-flags right shadow kerning))))
+ (cond
+ ((process-by-name "orb-placer" *active-pool*)
+ ;; orb-placer exists
+ ;; if its not enabled, kill it
+ (when (not (and *debug-segment* *orb-placer-enabled?*))
+ (kill-by-name "orb-placer" *active-pool*)
+ ;; make sure target is ungrabbed
+ (when (and *target* (= (-> *target* next-state name) 'target-grab))
+ (send-event *target* 'end-mode))
+ ;; release freecam if no target
+ (when (and (not *target*) (not (send-event *camera* 'query-state cam-free-floating)))
+ (send-event *camera* 'change-state cam-free-floating 0))))
+ (else
+ ;; orb-placer doesn't exist
+ ;; if its enabled, spawn it
+ (when (and *debug-segment* *orb-placer-enabled?*)
+ ;; process-spawn-function, spawns a process that runs the function you give it
+ (process-spawn-function process
+ :name "orb-placer"
+ (lambda :behavior process ()
+ (stack-size-set! (-> self top-thread) 512)
+ ;; Code before the loop runs once on process spawn
+ (let ((pad (-> *cpad-list* cpads 0))
+ (vec *orb-offset-tmp-vec*))
+ (loop
+ ;; Loop runs once per frame while process is active
+ ;; make sure target is grabbed
+ (when (and *target* (!= (-> *target* next-state name) 'target-grab))
+ (send-event *target* 'change-mode 'grab))
+ ;; lock freecam if no target
+ (when (and (not *target*) (not (send-event *camera* 'query-state cam-fixed)))
+ (send-event *camera* 'change-state cam-fixed 0))
+ ;; if we have an orb selected and the handle is nonzero...
+ (when (and (>= *orb-placer-selected-idx* 0) (nonzero? (-> *orb-placer-orbs* *orb-placer-selected-idx*)))
+ (let ((orb (the money (handle->process (-> *orb-placer-orbs* *orb-placer-selected-idx*)))))
+ (when orb
+ ;; highlight it
+ (orb-placer-highlight orb #t)
+ ;; respond to controller input
+ ;; X/Z based on camera
+ (when (nonzero? (-> pad stick0-speed))
+ (set! (-> vec x) (sin (-> pad stick0-dir)))
+ (set! (-> vec y) 0.0)
+ (set! (-> vec z) (cos (-> pad stick0-dir)))
+ (set! (-> vec w) 0.0)
+ ;; camera magic
+ (vector-matrix*! vec vec (matrix-local->world #t #f))
+ (vector-flatten! vec vec (-> *camera* local-down))
+ (vector-float*! vec vec (* (-> pad stick0-speed) 512.0)) ;; TODO scale?
+ ;; actually move orb
+ (vector+! (-> orb root trans) (-> orb root trans) vec)
+ (vector+! (-> orb base) (-> orb base) vec))
+ (cond
+ ;; fine tune/axis-aligned X/Z
+ ((cpad-pressed? 0 down) (+! (-> orb root trans z) (meters 0.03)) (+! (-> orb base z) (meters 0.03)))
+ ((cpad-pressed? 0 up) (+! (-> orb root trans z) (meters -0.03)) (+! (-> orb base z) (meters -0.03)))
+ ((cpad-pressed? 0 right) (+! (-> orb root trans x) (meters 0.03)) (+! (-> orb base x) (meters 0.03)))
+ ((cpad-pressed? 0 left) (+! (-> orb root trans x) (meters -0.03)) (+! (-> orb base x) (meters -0.03)))
+ ;; Y (up/down)
+ ((cpad-hold? 0 r2) (+! (-> orb root trans y) (meters 0.08)) (+! (-> orb base y) (meters 0.08)))
+ ((cpad-hold? 0 l2) (+! (-> orb root trans y) (meters -0.08)) (+! (-> orb base y) (meters -0.08)))
+ ((cpad-pressed? 0 r1) (+! (-> orb root trans y) (meters 0.03)) (+! (-> orb base y) (meters 0.03)))
+ ((cpad-pressed? 0 l1) (+! (-> orb root trans y) (meters -0.03)) (+! (-> orb base y) (meters -0.03)))))))
+ ;; Processes should suspend themselves, the loop will resume next frame
+ (suspend)))))))))
+ (none))
diff --git a/goal_src/jak1/engine/nav/navigate-h.gc b/goal_src/jak1/engine/nav/navigate-h.gc
index ff445018cb..29d3b4871f 100644
--- a/goal_src/jak1/engine/nav/navigate-h.gc
+++ b/goal_src/jak1/engine/nav/navigate-h.gc
@@ -126,7 +126,8 @@
(vertex (inline-array nav-vertex))
(poly-count int32)
(poly (inline-array nav-poly))
- (route (inline-array vector4ub)))
+ (route (inline-array vector4ub))
+ (custom-hacky? symbol))
(:methods
(tri-centroid-world (_type_ nav-poly vector) vector) ;; finds the centroid of the given triangle, in the "world" coordinate system.
(tri-centroid-local (_type_ nav-poly vector) vector) ;; finds the centroid of the given triangle, in the local nav-mesh coordinate system.
@@ -164,6 +165,172 @@
;; not sure why it's separate from 27 (and such a different implementation). there might be some details I'm missing here.
(is-in-mesh? (_type_ vector float meters) symbol)))
+(defmethod inspect nav-mesh
+ ((obj nav-mesh))
+ (format #t "(new 'static 'nav-mesh~%" obj (-> obj type))
+ (format #t
+ "~T:bounds (new 'static 'sphere :x (meters ~f) :y (meters ~f) :z (meters ~f) :w (meters ~f))~%"
+ (/ (-> obj bounds x) 4096)
+ (/ (-> obj bounds y) 4096)
+ (/ (-> obj bounds z) 4096)
+ (/ (-> obj bounds w) 4096))
+ (format #t
+ "~T:origin (new 'static 'vector :x (meters ~f) :y (meters ~f) :z (meters ~f) :w ~f)~%"
+ (/ (-> obj origin x) 4096)
+ (/ (-> obj origin y) 4096)
+ (/ (-> obj origin z) 4096)
+ (-> obj origin w))
+ #|
+(format #t "~Tnode-count: ~D~%" (-> obj node-count))
+ (let ((i 0))
+ (while (< i (-> obj node-count))
+ (format #t "~T--NODE ~d:~%" i)
+ (format #t "~T~Tcenter-x: ~f~%" (-> obj nodes i center-x))
+ (format #t "~T~Tcenter-y: ~f~%" (-> obj nodes i center-y))
+ (format #t "~T~Tcenter-z: ~f~%" (-> obj nodes i center-z))
+ (format #t "~T~Ttype: ~D~%" (-> obj nodes i type))
+ (format #t "~T~Tparent-offset: ~D~%" (-> obj nodes i parent-offset))
+ (format #t "~T~Tcenter: #~%" (&-> obj nodes i center-x))
+ (format #t "~T~Tradius-x: ~f~%" (-> obj nodes i radius-x))
+ (format #t "~T~Tradius-y: ~f~%" (-> obj nodes i radius-y))
+ (format #t "~T~Tradius-z: ~f~%" (-> obj nodes i radius-z))
+ (format #t "~T~Tleft-offset: ~D~%" (-> obj nodes i left-offset))
+ (format #t "~T~Tright-offset: ~D~%" (-> obj nodes i right-offset))
+ (format #t "~T~Tnum-tris: ~D~%" (-> obj nodes i num-tris))
+ (format #t "~T~Tradius: #~%" (&-> obj nodes i radius-x))
+ (format #t "~T~Tscale-x: ~f~%" (-> obj nodes i scale-x))
+ (format #t "~T~Tfirst-tris[4] @ #x~X~%" (-> obj nodes i first-tris))
+ (format #t "~T~Tscale-z: ~f~%" (-> obj nodes i scale-z))
+ (format #t "~T~Tlast-tris[4] @ #x~X~%" (-> obj nodes i last-tris))
+ (format #t "~T~Tscale: #~%" (&-> obj nodes i scale-x))
+ (format #t "~T----------~%" (&-> obj nodes i scale-x))
+ (+! i 1)
+ )
+ )
+ |#
+ (format #t "~T:vertex-count ~D~%" (-> obj vertex-count))
+ (format #t "~T:vertex (new 'static 'inline-array nav-vertex ~D~%" (-> obj vertex-count))
+ (let ((i 0))
+ (while (< i (-> obj vertex-count))
+ (format #t
+ "~T~T(new 'static 'nav-vertex :x (meters ~f) :y (meters ~f) :z (meters ~f) :w ~f)~%"
+ (/ (-> obj vertex i x) 4096)
+ (/ (-> obj vertex i y) 4096)
+ (/ (-> obj vertex i z) 4096)
+ (-> obj vertex i w))
+ (+! i 1)))
+ (format #t "~T)~%")
+ (format #t "~T:poly-count ~D~%" (-> obj poly-count))
+ (format #t "~T:poly (new 'static 'inline-array nav-poly ~D~%" (-> obj poly-count))
+ (let ((i 0))
+ (while (< i (-> obj poly-count))
+ (format #t "~T~T(new 'static 'nav-poly~%")
+ (format #t "~T~T:id #x~X~%" (-> obj poly i id))
+ (format #t
+ "~T~T:vertex (new 'static 'array uint8 3 #x~X #x~X #x~X)~%"
+ (-> obj poly i vertex 0)
+ (-> obj poly i vertex 1)
+ (-> obj poly i vertex 2))
+ (format #t
+ "~T~T:adj-poly (new 'static 'array uint8 3 #x~X #x~X #x~X)~%"
+ (-> obj poly i adj-poly 0)
+ (-> obj poly i adj-poly 1)
+ (-> obj poly i adj-poly 2))
+ (format #t "~T~T:pat ~D~%" (-> obj poly i pat))
+ (format #t "~T~T)~%")
+ (+! i 1)))
+ (format #t "~T)~%")
+ (format #t "~T:node-count ~D~%" (-> obj node-count))
+ (format #t "~T:nodes (new 'static 'inline-array nav-node ~d~%" (-> obj node-count))
+ (let ((i 0))
+ (while (< i (-> obj node-count))
+ (format #t
+ "~T~T(new 'static 'nav-node :center-x (meters ~f) :center-y (meters ~f) :center-z (meters ~f) :type ~d :parent-offset ~d~%"
+ (/ (-> obj nodes i center-x) 4096)
+ (/ (-> obj nodes i center-y) 4096)
+ (/ (-> obj nodes i center-z) 4096)
+ (-> obj nodes i type)
+ (-> obj nodes i parent-offset))
+ (format #t
+ "~T~T:center (new 'static 'vector :x (meters ~f) :y (meters ~f) :z (meters ~f) :w ~f)~%"
+ (/ (-> obj nodes i center x) 4096)
+ (/ (-> obj nodes i center y) 4096)
+ (/ (-> obj nodes i center z) 4096)
+ (-> obj nodes i center w))
+ (format #t
+ "~T~T:radius-x (meters ~f) :radius-y (meters ~f) :radius-z (meters ~f)~%"
+ (/ (-> obj nodes i radius-x) 4096)
+ (/ (-> obj nodes i radius-y) 4096)
+ (/ (-> obj nodes i radius-z) 4096))
+ (format #t
+ "~T~T:left-offset ~d :right-offset ~d :num-tris ~d~%"
+ (-> obj nodes i left-offset)
+ (-> obj nodes i right-offset)
+ (-> obj nodes i num-tris))
+ (format #t
+ "~T~T:radius (new 'static 'vector :x (meters ~f) :y (meters ~f) :z (meters ~f) :w ~f) :scale-x (meters ~f)~%"
+ (/ (-> obj nodes i radius x) 4096)
+ (/ (-> obj nodes i radius y) 4096)
+ (/ (-> obj nodes i radius z) 4096)
+ (-> obj nodes i radius w)
+ (/ (-> obj nodes i scale-x) 4096))
+ (format #t
+ "~T~T:first-tris (new 'static 'array uint8 4 #x~X #x~X #x~X #x~X) :scale-z (meters ~f)~%"
+ (-> obj nodes i first-tris 0)
+ (-> obj nodes i first-tris 1)
+ (-> obj nodes i first-tris 2)
+ (-> obj nodes i first-tris 3)
+ (/ (-> obj nodes i scale-z) 4096))
+ (format #t
+ "~T~T:last-tris (new 'static 'array uint8 4 #x~X #x~X #x~X #x~X)~%"
+ (-> obj nodes i last-tris 0)
+ (-> obj nodes i last-tris 1)
+ (-> obj nodes i last-tris 2)
+ (-> obj nodes i last-tris 3))
+ (format #t
+ "~T~T:scale (new 'static 'vector :x (meters ~f) :y ~f :z (meters ~f) :w ~f))~%"
+ (/ (-> obj nodes i scale x) 4096)
+ (-> obj nodes i scale y)
+ (/ (-> obj nodes i scale z) 4096)
+ (-> obj nodes i scale w))
+ (+! i 1)))
+ #|
+(deftype nav-node (structure)
+ ((center-x float :offset-assert 0)
+ (center-y float :offset-assert 4)
+ (center-z float :offset-assert 8)
+ (type uint16 :offset-assert 12)
+ (parent-offset uint16 :offset-assert 14)
+ (center vector :inline :offset 0)
+ (radius-x float :offset-assert 16)
+ (radius-y float :offset-assert 20)
+ (radius-z float :offset-assert 24)
+ (left-offset uint16 :offset-assert 28)
+ (right-offset uint16 :offset-assert 30)
+ (num-tris uint32 :offset 28)
+ (radius vector :inline :offset 16)
+ (scale-x float :offset-assert 32)
+ (first-tris uint8 4 :offset-assert 36)
+ (scale-z float :offset-assert 40)
+ (last-tris uint8 4 :offset-assert 44)
+ (scale vector :inline :offset 32)
+ )
+ |#
+ (format #t "~T)~%")
+ (format #t "~T:route (new 'static 'inline-array vector4ub ~d~%" (* (-> obj poly-count) 2))
+ (let ((i 0))
+ (while (< i (* (-> obj poly-count) 2))
+ (format #t
+ "~T~T(new 'static 'vector4ub :data (new 'static 'array uint8 4 #x~X #x~X #x~X #x~X))~%"
+ (-> obj route i data 0)
+ (-> obj route i data 1)
+ (-> obj route i data 2)
+ (-> obj route i data 3))
+ (+! i 1)))
+ (format #t "~T)~%")
+ (format #t ")~%")
+ obj)
+
(define-extern *default-nav-mesh* nav-mesh)
(deftype check-vector-collision-with-nav-spheres-info (structure)
diff --git a/goal_src/jak1/engine/nav/navigate.gc b/goal_src/jak1/engine/nav/navigate.gc
index 872348c4d1..ed87c8297d 100644
--- a/goal_src/jak1/engine/nav/navigate.gc
+++ b/goal_src/jak1/engine/nav/navigate.gc
@@ -368,29 +368,30 @@
#f))
(defun recursive-inside-poly ((arg0 nav-mesh) (arg1 nav-node) (arg2 vector) (arg3 float))
- (when (point-inside-rect? arg1 arg2 arg3)
- (cond
- ((zero? (-> arg1 type))
- (let ((v1-2 (-> arg1 left-offset))
- (s3-0 (-> arg1 right-offset)))
- (when (>= v1-2 0)
- (let* ((a1-2 (the-as nav-node (&+ (the-as (pointer nav-node) (-> arg0 nodes)) v1-2)))
- (v1-3 (recursive-inside-poly arg0 a1-2 arg2 arg3)))
- (if (>= v1-3 0) (return v1-3))))
- (when (>= s3-0 0)
- (let* ((a1-3 (&+ (the-as (pointer nav-node) (-> arg0 nodes)) s3-0))
- (v1-7 (recursive-inside-poly arg0 (the-as nav-node a1-3) arg2 arg3)))
- (if (>= v1-7 0) (return v1-7)))))
- (return -1))
- (else
- (let ((s3-1 (-> arg1 num-tris))
- (s2-1 (-> arg1 first-tris)))
- (dotimes (s1-0 (the-as int s3-1))
- (let ((s0-0 (-> s2-1 0))) (if (point-inside-poly? arg0 s0-0 arg2 arg3) (return (the-as int s0-0))))
- (set! s2-1 (&-> s2-1 1))
- (if (= s1-0 3) (set! s2-1 (&-> s2-1 4)))))
- (return -1)))
- (the-as none 0))
+ (when (not (true? (-> arg0 custom-hacky?))) ;; This function crashes our custom navmeshes, so we just skip it for them. Doesn't seem to be important anyway.
+ (when (point-inside-rect? arg1 arg2 arg3)
+ (cond
+ ((zero? (-> arg1 type))
+ (let ((v1-2 (-> arg1 left-offset))
+ (s3-0 (-> arg1 right-offset)))
+ (when (>= v1-2 0)
+ (let* ((a1-2 (the-as nav-node (&+ (the-as (pointer nav-node) (-> arg0 nodes)) v1-2)))
+ (v1-3 (recursive-inside-poly arg0 a1-2 arg2 arg3)))
+ (if (>= v1-3 0) (return v1-3))))
+ (when (>= s3-0 0)
+ (let* ((a1-3 (&+ (the-as (pointer nav-node) (-> arg0 nodes)) s3-0))
+ (v1-7 (recursive-inside-poly arg0 (the-as nav-node a1-3) arg2 arg3)))
+ (if (>= v1-7 0) (return v1-7)))))
+ (return -1))
+ (else
+ (let ((s3-1 (-> arg1 num-tris))
+ (s2-1 (-> arg1 first-tris)))
+ (dotimes (s1-0 (the-as int s3-1))
+ (let ((s0-0 (-> s2-1 0))) (if (point-inside-poly? arg0 s0-0 arg2 arg3) (return (the-as int s0-0))))
+ (set! s2-1 (&-> s2-1 1))
+ (if (= s1-0 3) (set! s2-1 (&-> s2-1 4)))))
+ (return -1)))
+ (the-as none 0)))
-1)
(defmethod find-poly-fast ((this nav-mesh) (arg0 vector) (arg1 meters))
@@ -932,7 +933,9 @@
(format #t "WARNING: nav-mesh only contains gap triangles (~D triangles total). " s5-0)
(set! gp-0 #t))
(when gp-0
- (if pp (format #t "current process is ~A~%" (-> pp name)) (format #t "(no current process).~%"))))
+ (if pp (format #t "current process is ~A~%" (-> pp name)) (format #t "(no current process).~%")))
+ (when (zero? (-> this custom-hacky?))
+ (set! (-> this custom-hacky?) #f)))
0
(none)))
@@ -1087,6 +1090,18 @@
(bucket-id debug-no-zbuf)
(vector+! s4-0 (-> s5-0 origin) (the-as vector (-> s5-0 vertex s3-0)))
*color-green*)))
+ (when *display-nav-marks-extras*
+ (dotimes (s3-0 (-> s5-0 vertex-count))
+ (let ((s3-1 add-debug-text-3d)
+ (s2-1 #t)
+ (s1-0 68))
+ (format (clear *temp-string*) "~D" s3-0)
+ (s3-1 s2-1
+ (bucket-id debug-no-zbuf)
+ *temp-string*
+ (vector+! s4-0 (the-as vector (-> s5-0 vertex s3-0)) (-> s5-0 origin))
+ (font-color pink)
+ (the-as vector2h #f)))))
(when #f
(dotimes (s3-1 (-> s5-0 node-count))
(let ((a1-4 (-> s5-0 nodes s3-1))
@@ -1136,7 +1151,7 @@
((logtest? (-> s2-1 pat) 8) (new 'static 'rgba :g #x80 :b #x40 :a #xff))
((logtest? (-> s2-1 pat) 16) (new 'static 'rgba :g #x80 :b #x40 :a #xff))
(else (new 'static 'rgba :g #x80 :b #xff :a #x80))))
- (when (logtest? (-> this flags) (nav-control-flags navcf4))
+ (when (or (logtest? (-> this flags) (nav-control-flags navcf4)) *display-nav-marks-extras*)
(let ((s1-1 add-debug-text-3d)
(s0-1 #t))
(set! sv-224 68)
diff --git a/goal_src/jak1/engine/ps2/pad.gc b/goal_src/jak1/engine/ps2/pad.gc
index 37dd0aa947..f0302fe4a6 100644
--- a/goal_src/jak1/engine/ps2/pad.gc
+++ b/goal_src/jak1/engine/ps2/pad.gc
@@ -397,6 +397,28 @@
(defmacro cpad-pressed? (pad-idx &rest buttons)
`(logtest? (cpad-pressed ,pad-idx) (pad-buttons ,@buttons)))
+(defenum dpad-idx
+ (none)
+ (up)
+ (right)
+ (down)
+ (left)
+ )
+
+(defun cpad-dpad-pressed? ((idx int))
+ (cond
+ ((cpad-pressed? idx up)
+ (return (dpad-idx up)))
+ ((cpad-pressed? idx down)
+ (return (dpad-idx down)))
+ ((cpad-pressed? idx left)
+ (return (dpad-idx left)))
+ ((cpad-pressed? idx right)
+ (return (dpad-idx right)))
+ )
+ #f
+)
+
(defmacro cpad-hold? (pad-idx &rest buttons)
`(logtest? (cpad-hold ,pad-idx) (pad-buttons ,@buttons)))
diff --git a/goal_src/jak1/engine/target/target-death.gc b/goal_src/jak1/engine/target/target-death.gc
index 84b19dd11b..bae0b23235 100644
--- a/goal_src/jak1/engine/target/target-death.gc
+++ b/goal_src/jak1/engine/target/target-death.gc
@@ -7,6 +7,7 @@
(require "engine/target/target-part.gc")
;; DECOMP BEGINS
+(define-extern runs-on-jak-spawn (function none))
(defskelgroup *deathcam-sg*
deathcam
@@ -284,6 +285,7 @@
(car (-> s5-8 continues))))))
(ja-channel-set! 1)
(ja :group! eichar-stance-loop-ja)
+ (runs-on-jak-spawn) ;; trigger hook for mod base mod-base-change
(suspend)
(logior! (-> self control status) (cshape-moving-flags onsurf onground tsurf))
(go target-stance))
@@ -684,6 +686,7 @@
trans)
:code
(behavior ((arg0 symbol))
+ (runs-on-jak-death 'dying) ;;mod-base-change
(set! (-> self control unknown-uint20) (the-as uint #f))
(set! (-> self control unknown-int21)
(the-as int (send-event (handle->process (-> self attack-info attacker)) 'target 'die arg0)))
diff --git a/goal_src/jak1/engine/ui/text-h.gc b/goal_src/jak1/engine/ui/text-h.gc
index b56a8d426b..681a61d585 100644
--- a/goal_src/jak1/engine/ui/text-h.gc
+++ b/goal_src/jak1/engine/ui/text-h.gc
@@ -790,6 +790,11 @@
(input-opts-binds-unknown #x1615)
(progress-no-other-resolution-options #x1616)
(input-opts-controller-led-reflect-heat #x1617)
+ ;; mod text mod-base-change
+ (pad-triangle #x2000)
+ (pad-circle #x2001)
+ (pad-x #x2002)
+ (pad-square #x2003)
;; GAME-TEXT-ID ENUM ENDS
)
diff --git a/goal_src/jak1/game.gp b/goal_src/jak1/game.gp
index da6370fe95..3fc6b1f3db 100644
--- a/goal_src/jak1/game.gp
+++ b/goal_src/jak1/game.gp
@@ -492,6 +492,7 @@
"village_common/oracle.gc"
"common/blocking-plane.gc"
+ "common/blocking-plane-b.gc" ;; mod-base-change
"common/launcherdoor.gc"
"common/battlecontroller.gc"
@@ -1748,7 +1749,7 @@
:deps
("$OUT/obj/display.o"
"$OUT/obj/decomp-h.o")
-
+
"engine/connect.gc"
"ui/text-h.gc"
"game/settings-h.gc"
@@ -2046,6 +2047,7 @@
"common-obs/plat.gc"
"common-obs/plat-button.gc"
"common-obs/plat-eco.gc"
+ "common-obs/linear-plat.gc"
"common-obs/ropebridge.gc"
"common-obs/ticky.gc"
)
@@ -2101,8 +2103,24 @@
(goal-src "pc/debug/default-menu-pc.gc" "anim-tester-x" "part-tester" "entity-debug")
(goal-src "pc/debug/pc-debug-common.gc" "pckernel-impl" "entity-h" "game-info-h" "level-h" "settings-h" "gsound-h" "target-util")
(goal-src "pc/debug/pc-debug-methods.gc" "pc-debug-common")
+
+(goal-src "engine/mods/input-display.gc")
+(goal-src "engine/mods/orb-placer.gc")
+
+
+(goal-src-sequence
+ ;; prefix
+ "engine/"
+ :deps ("$OUT/obj/battlecontroller.o" "$OUT/obj/snow-bunny.o" "$OUT/obj/baby-spider.o" "$OUT/obj/sage-village3.o" "$OUT/obj/sage-finalboss.o" "$OUT/obj/assistant-citadel.o" "$OUT/obj/assistant-lavatube.o" "$OUT/obj/robocave-part.o" "$OUT/obj/driller-lurker.o" "$OUT/obj/training-part.o" "$OUT/obj/rolling-race-ring.o" "$OUT/obj/beach-part.o" "$OUT/obj/sculptor.o" "$OUT/obj/sunken-fish.o" "$OUT/obj/billy.o" "$OUT/obj/sidekick-human.o" "$OUT/obj/flying-lurker.o" "$OUT/obj/target-racer-h.o" "$OUT/obj/firecanyon-obs.o" "$OUT/obj/target-flut.o" "$OUT/obj/hud-classes-pc.o" "$OUT/obj/collide-reaction-racer.o" "$OUT/obj/plant-boss.o" "$OUT/obj/beach-obs.o" "$OUT/obj/sunken-elevator.o" "$OUT/obj/jungle-part.o" "$OUT/obj/sequence-a-village1.o" "$OUT/obj/ticky.o")
+ "mods/mod-settings.gc"
+ "mods/mod-common-functions.gc"
+ "mods/mod-custom-code.gc"
+ "mods/mod-debug.gc"
+)
+
(goal-src "levels/test-zone/test-zone-obs.gc" "process-drawable")
+
(group-list "all-code"
`(,@(reverse *all-gc*))
)
diff --git a/goal_src/jak1/kernel-defs.gc b/goal_src/jak1/kernel-defs.gc
index f2a37ba747..be0a08c9ef 100644
--- a/goal_src/jak1/kernel-defs.gc
+++ b/goal_src/jak1/kernel-defs.gc
@@ -535,6 +535,25 @@
(define-extern pc-discord-rpc-set (function int none))
+;;main music start
+(define-extern play-main-music (function string int none))
+
+(define-extern pause-main-music (function none))
+
+(define-extern stop-main-music (function none))
+
+(define-extern resume-main-music (function none))
+
+(define-extern main-music-volume (function int none))
+
+;;main music end
+
+(define-extern play-sound-file (function string int none))
+
+(define-extern stop-sound-file (function string none))
+
+(define-extern stop-all-sounds (function none))
+
(define-extern pc-filepath-exists? (function string symbol))
(define-extern pc-mkdir-file-path (function string none))
diff --git a/goal_src/jak1/levels/common/blocking-plane-b.gc b/goal_src/jak1/levels/common/blocking-plane-b.gc
new file mode 100644
index 0000000000..4e8c54088f
--- /dev/null
+++ b/goal_src/jak1/levels/common/blocking-plane-b.gc
@@ -0,0 +1,113 @@
+;;-*-Lisp-*-
+(in-package goal)
+(bundles "FIC.DGO" "LAV.DGO" "MIS.DGO" "OGR.DGO" "ROL.DGO" "SNO.DGO" "SWA.DGO")
+(require "engine/geometry/path.gc")
+(require "engine/common-obs/process-drawable.gc")
+
+;; DECOMP BEGINS
+
+(deftype blocking-plane-b (process-drawable) ()
+ (:states
+ blocking-plane-b-idle))
+
+(defskelgroup *ef-plane-sg*
+ ef-plane
+ ef-plane-lod0-jg
+ ef-plane-idle-ja
+ ((ef-plane-lod0-mg (meters 999999)))
+ :bounds (static-spherem 0 0 0 30))
+
+(defstate blocking-plane-b-idle (blocking-plane-b)
+ :trans
+ (behavior ()
+ (if (and *debug-segment* *display-path-marks*)
+ (logclear! (-> self draw status) (draw-status skip-bones)) ;; in debug, show blocking planes when path marks on
+ (logior! (-> self draw status) (draw-status skip-bones))))
+ :code
+ (behavior ()
+ (transform-post)
+ (loop
+ (when (not *debug-segment*)
+ (logior! (-> self mask) (process-mask sleep)))
+ (suspend))))
+
+(defbehavior blocking-plane-b-init-by-other blocking-plane-b ((vec-pair (inline-array vector)) (height float))
+ "Rewritten based on Jak II implementation - sets up plane given 2 vectors and a height"
+ (if (not vec-pair) (deactivate self))
+ (let ((s5-0 (-> vec-pair 0))
+ (gp-0 (-> vec-pair 1)))
+ 0.0
+ (let ((f30-1 (* 0.5 (vector-vector-distance s5-0 gp-0)))
+ (s4-1 (new 'process 'collide-shape self (collide-list-enum usually-hit-by-player))))
+ (let ((s3-1 (new 'process 'collide-shape-prim-mesh s4-1 (the-as uint 0) (the-as uint 0))))
+ (set! (-> s3-1 prim-core collide-as) (collide-kind wall-object))
+ (set! (-> s3-1 collide-with) (collide-kind target))
+ (set! (-> s3-1 prim-core action) (collide-action solid))
+ (set! (-> s3-1 prim-core offense) (collide-offense indestructible))
+ (set! (-> s3-1 transform-index) 3)
+ (set-vector! (-> s3-1 local-sphere) 0.0 (* 0.00024414062 (* 0.5 height)) 0.0 (fmax height f30-1))
+ (set-root-prim! s4-1 s3-1))
+ (set! (-> s4-1 nav-radius) (* 0.75 (-> s4-1 root-prim local-sphere w)))
+ (backup-collide-with-as s4-1)
+ (set! (-> self root) s4-1))
+ (let ((s4-2 (new-stack-matrix0)))
+ (vector+! (-> self root trans) s5-0 gp-0)
+ (vector-float*! (-> self root trans) (-> self root trans) 0.5)
+ (+! (-> self root trans y) (* 0.5 height))
+ (vector-! (the-as vector (-> s4-2 vector)) gp-0 s5-0)
+ (set! (-> self root scale x) (* 0.00024414062 (vector-normalize-ret-len! (the-as vector (-> s4-2 vector)) 1.0)))
+ (set! (-> self root scale y) (* 0.00024414062 height))
+ (set! (-> self root scale z) 0.0)
+ (set! (-> s4-2 vector 1 quad) (-> (new 'static 'vector :y 1.0 :w 1.0) quad))
+ (vector-cross! (-> s4-2 vector 2) (the-as vector (-> s4-2 vector)) (-> s4-2 vector 1))
+ (vector-normalize! (-> s4-2 vector 2) 1.0)
+ (matrix->quaternion (-> self root quat) s4-2)))
+ (initialize-skeleton self *ef-plane-sg* '())
+ (logior! (-> self draw status) (draw-status skip-bones))
+ (go blocking-plane-b-idle)
+ (none))
+
+;; Use the below function to spawn a blocking-plane-b between 2 vectors with a given height.
+;;
+;; Example (spawn):
+;;
+;; (when (not (process-by-name "test-blocking-plane-b-1" *active-pool*))
+;; (let ((verts (new 'stack-no-clear 'inline-array 'vector 2)))
+;; (set-vector-meters! (-> verts 0) 194.39 10.62 -1587.11) ;; near geologist
+;; (set-vector-meters! (-> verts 1) 214.60 10.31 -1590.41)
+;; (blocking-plane-b-spawn-simple verts (meters 30.0) "test-blocking-plane-b-1")
+;; )
+;; )
+;;
+;; Example (despawn):
+;;
+;; (let ((plane (process-by-name "test-blocking-plane-b-1" *active-pool*)))
+;; (when plane (deactivate plane))
+;; )
+(defun blocking-plane-b-spawn-simple ((verts (inline-array vector)) (height float) (name string))
+ (process-spawn blocking-plane-b verts height :to *active-pool* :name name))
+
+(defbehavior blocking-plane-b-spawn process ((arg0 curve-control))
+ (cond
+ ((or (not arg0) (logtest? (-> arg0 flags) (path-control-flag not-found))))
+ (else
+ (let ((s5-0 (the int (the float (+ (-> arg0 curve num-cverts) -1))))
+ (s2-0 (new 'stack-no-clear 'inline-array 'vector 2))
+ (s4-0 0))
+ (while (< s4-0 s5-0)
+ (eval-path-curve-div! arg0 (-> s2-0 0) (the float s4-0) 'exact)
+ (eval-path-curve-div! arg0 (-> s2-0 1) (+ 1.0 (the float s4-0)) 'exact)
+ (process-spawn blocking-plane-b s2-0 122880.0 :to self)
+ (+! s4-0 2)))))
+ 0
+ (none))
+
+(defun blocking-plane-b-destroy ()
+ (with-pp
+ (let ((gp-0 (-> pp child)))
+ (while gp-0
+ (let ((s5-0 (ppointer->process gp-0)))
+ (set! gp-0 (-> gp-0 0 brother))
+ (if (type-type? (-> s5-0 type) blocking-plane-b) (deactivate s5-0)))))
+ 0
+ (none)))
diff --git a/goal_src/jak1/levels/flut_common/flutflut.gc b/goal_src/jak1/levels/flut_common/flutflut.gc
index 42affa6c5d..bc3162e70e 100644
--- a/goal_src/jak1/levels/flut_common/flutflut.gc
+++ b/goal_src/jak1/levels/flut_common/flutflut.gc
@@ -7,6 +7,8 @@
;; DECOMP BEGINS
+(define-extern *allow-flutflut-anywhere* symbol)
+
(if (not (nmember "flutp" *kernel-packages*)) (set! *kernel-packages* (cons "flutp" *kernel-packages*)))
(deftype flutflut (process-drawable)
@@ -124,7 +126,8 @@
:enter
(behavior ()
(blocking-plane-destroy)
- (blocking-plane-spawn (the-as curve-control (-> self path-target))))
+ (if (not *allow-flutflut-anywhere*) (blocking-plane-spawn (the-as curve-control (-> self path-target))))
+ (none))
:exit
(-> (method-of-type flutflut wait-for-start)
exit)
@@ -153,8 +156,11 @@
(let ((v1-35 gp-0)) (set! (-> v1-35 height) (the float 80)))
(set! (-> gp-0 flags) (font-flags shadow kerning large))
(print-game-text (lookup-text! *common-text* (text-id press-to-use) #f) gp-0 #f 128 22))
- (if (and (cpad-pressed? 0 circle) (send-event *target* 'change-mode 'flut self))
- (go-virtual pickup (method-of-object self wait-for-return)))))
+ (if *allow-flutflut-anywhere* ;; if "self" is passed instead of #f, deloading level/flutflut will crash
+ (if (and (cpad-pressed? 0 circle) (send-event *target* 'change-mode 'flut #f))
+ (go-virtual pickup (method-of-object self wait-for-return)))
+ (if (and (cpad-pressed? 0 circle) (send-event *target* 'change-mode 'flut self))
+ (go-virtual pickup (method-of-object self wait-for-return))))))
(flutflut-effect)
(suspend)
(ja :num! (loop!))))
@@ -200,7 +206,8 @@
:enter
(behavior ()
(blocking-plane-destroy)
- (blocking-plane-spawn (the-as curve-control (-> self path-flut))))
+ (if (not *allow-flutflut-anywhere*) (blocking-plane-spawn (the-as curve-control (-> self path-flut))))
+ (none))
:code
(behavior ()
(ja-channel-set! 0)
@@ -243,10 +250,11 @@
(set! (-> this auto-get-off) #f)
(move-to-ground (-> this root) 40960.0 40960.0 #t (collide-kind background))
(set! (-> this cell) (the-as handle #f))
- (blocking-plane-spawn (the-as curve-control
- (if (and *target* (logtest? (-> *target* control root-prim prim-core action) (collide-action flut)))
- (-> this path-flut)
- (-> this path-target))))
+ (if (not *allow-flutflut-anywhere*)
+ (blocking-plane-spawn (the-as curve-control
+ (if (and *target* (logtest? (-> *target* control root-prim prim-core action) (collide-action flut)))
+ (-> this path-flut)
+ (-> this path-target)))))
(set! (-> this sound) (new 'process 'ambient-sound (static-sound-spec "zoom-teleport" :fo-max 30) (-> this root trans)))
(go (method-of-object this wait-for-start))
(none))
diff --git a/goal_src/jak1/levels/racer_common/racer.gc b/goal_src/jak1/levels/racer_common/racer.gc
index 6fcdf7a54d..196ca3bde5 100644
--- a/goal_src/jak1/levels/racer_common/racer.gc
+++ b/goal_src/jak1/levels/racer_common/racer.gc
@@ -5,6 +5,8 @@
(require "engine/common-obs/generic-obs.gc")
(require "engine/entity/ambient.gc")
(require "engine/game/task/task-control.gc")
+(define-extern *allow-zoomer-anywhere* symbol)
+
(define-extern blocking-plane-destroy (function none))
(define-extern blocking-plane-spawn (function curve-control none :behavior process))
@@ -149,36 +151,38 @@
:enter
(behavior ()
(blocking-plane-destroy)
- (blocking-plane-spawn (-> self path-target)))
- :exit
- (-> (method-of-type racer wait-for-start)
- exit)
+ (if (not *allow-zoomer-anywhere*) (blocking-plane-spawn (-> self path-target)))
+ (none)) ;; mod-base-change
+ :exit (-> (method-of-type racer wait-for-start) exit)
:code
- (behavior ()
- (ja-channel-set! 1)
- (ja :group! racer-racer-idle-ja)
- (set! (-> self root root-prim prim-core action) (collide-action solid attackable-unused))
- (set! (-> self root root-prim prim-core offense) (collide-offense indestructible))
- 0.0
- (let ((f30-0 (if (= (-> self condition) 4) 61440.0 20480.0)))
- (loop
- (if (and *target* (logtest? (-> *target* control root-prim prim-core action) (collide-action racer)))
- (go-virtual wait-for-return))
- (when (and (and *target* (>= f30-0 (vector-vector-distance (-> self root trans) (-> *target* control trans))))
- (and (not (movie?)) (not (level-hint-displayed?))))
- (hide-hud)
- (level-hint-surpress!)
- (kill-current-level-hint '() '(sidekick voicebox) 'exit)
- (when (and (hud-hidden?) (can-grab-display? self))
- (let ((gp-0 (new 'stack 'font-context *font-default-matrix* 32 160 0.0 (font-color default) (font-flags shadow kerning))))
- (let ((v1-24 gp-0)) (set! (-> v1-24 width) (the float 440)))
- (let ((v1-25 gp-0)) (set! (-> v1-25 height) (the float 80)))
- (set! (-> gp-0 flags) (font-flags shadow kerning large))
- (print-game-text (lookup-text! *common-text* (text-id press-to-use) #f) gp-0 #f 128 22))
+ (behavior ()
+ (ja-channel-set! 1)
+ (ja :group! racer-racer-idle-ja)
+ (set! (-> self root root-prim prim-core action) (collide-action solid attackable-unused))
+ (set! (-> self root root-prim prim-core offense) (collide-offense indestructible))
+ 0.0
+ (let ((f30-0 (if (= (-> self condition) 4) 61440.0 20480.0)))
+ (loop
+ (if (and *target* (logtest? (-> *target* control root-prim prim-core action) (collide-action racer)))
+ (go-virtual wait-for-return))
+ (when (and (and *target* (>= f30-0 (vector-vector-distance (-> self root trans) (-> *target* control trans))))
+ (and (not (movie?)) (not (level-hint-displayed?))))
+ (hide-hud)
+ (level-hint-surpress!)
+ (kill-current-level-hint '() '(sidekick voicebox) 'exit)
+ (when (and (hud-hidden?) (can-grab-display? self))
+ (let ((gp-0 (new 'stack 'font-context *font-default-matrix* 32 160 0.0 (font-color default) (font-flags shadow kerning))))
+ (let ((v1-24 gp-0)) (set! (-> v1-24 width) (the float 440)))
+ (let ((v1-25 gp-0)) (set! (-> v1-25 height) (the float 80)))
+ (set! (-> gp-0 flags) (font-flags shadow kerning large))
+ (print-game-text (lookup-text! *common-text* (text-id press-to-use) #f) gp-0 #f 128 22))
+ (if *allow-zoomer-anywhere* ;; if "self" is passed instead of #f, deloading level/zoomer will crash ;;mod-base-change
+ (if (and (or (cpad-pressed? 0 circle) (= (-> self condition) 4)) (send-event *target* 'change-mode 'racing #f))
+ (go-virtual pickup (the-as (state collectable) (method-of-object self wait-for-return))))
(if (and (or (cpad-pressed? 0 circle) (= (-> self condition) 4)) (send-event *target* 'change-mode 'racing self))
- (go-virtual pickup (the-as (state collectable) (method-of-object self wait-for-return))))))
- (racer-effect)
- (suspend))))
+ (go-virtual pickup (the-as (state collectable) (method-of-object self wait-for-return)))))))
+ (racer-effect)
+ (suspend))))
:post ja-post)
(defstate pickup (racer)
@@ -238,7 +242,8 @@
:enter
(behavior ()
(blocking-plane-destroy)
- (blocking-plane-spawn (the-as curve-control (-> self path-racer))))
+ (if (not *allow-zoomer-anywhere*) (blocking-plane-spawn (the-as curve-control (-> self path-racer)))) ;;mod-base-chhange
+ (none))
:code
(behavior ()
(ja-channel-set! 0)
@@ -283,11 +288,12 @@
(set! (-> this auto-get-off) #f)
(move-to-ground (-> this root) 40960.0 40960.0 #t (collide-kind background))
(set! (-> this cell) (the-as handle #f))
- (blocking-plane-spawn (the-as curve-control
- (if (or (and *target* (logtest? (-> *target* control root-prim prim-core action) (collide-action racer)))
- (= (-> this condition) 3))
- (-> this path-racer)
- (-> this path-target))))
+ (if (not *allow-zoomer-anywhere*) ;;mod-base-change
+ (blocking-plane-spawn (the-as curve-control
+ (if (or (and *target* (logtest? (-> *target* control root-prim prim-core action) (collide-action racer)))
+ (= (-> this condition) 3))
+ (-> this path-racer)
+ (-> this path-target)))))
(set! (-> this sound) (new 'process 'ambient-sound (static-sound-spec "zoom-teleport" :fo-max 30) (-> this root trans)))
(go (method-of-object this wait-for-start))
(none))
diff --git a/goal_src/jak1/levels/test-zone/test-zone-obs.gc b/goal_src/jak1/levels/test-zone/test-zone-obs.gc
index 90725a1551..919591ac2f 100644
--- a/goal_src/jak1/levels/test-zone/test-zone-obs.gc
+++ b/goal_src/jak1/levels/test-zone/test-zone-obs.gc
@@ -119,4 +119,4 @@
; )
; )
(suspend)))
- :post transform-post)
+ :post transform-post)
\ No newline at end of file
diff --git a/goal_src/jak1/pc/features/autosplit.gc b/goal_src/jak1/pc/features/autosplit.gc
index 1dfbea9d42..00bdab8dc3 100644
--- a/goal_src/jak1/pc/features/autosplit.gc
+++ b/goal_src/jak1/pc/features/autosplit.gc
@@ -4,6 +4,8 @@
(require "pc/features/autosplit-h.gc")
(define *autosplit-info-jak1* (new 'static 'autosplit-info-jak1))
+(define-extern *has-landed?* symbol)
+
;; Setup markers
(charp<-string (-> *autosplit-info-jak1* end-marker) "end")
@@ -281,5 +283,6 @@
(none))
(defun update-autosplit-jak1-new-game ()
+ (false! *has-landed?*)
(set! (-> *autosplit-info-jak1* game-hash) (pc-get-unix-timestamp))
(none))
diff --git a/goal_src/jak1/pc/features/speedruns.gc b/goal_src/jak1/pc/features/speedruns.gc
index 6302daddef..10d70fc8d8 100644
--- a/goal_src/jak1/pc/features/speedruns.gc
+++ b/goal_src/jak1/pc/features/speedruns.gc
@@ -6,6 +6,10 @@
(require "pc/features/autosplit-h.gc")
(define *speedrun-info* (new 'static 'speedrun-info-jak1 :should-display? #t :needs-post-blackout-setup? #f))
+(define *has-landed?* #f)
+
+(define-extern *mod-version-text* string)
+
(define *hub1-cell-list*
(new 'static
'boxed-array
@@ -48,6 +52,48 @@
(game-task firecanyon-end)
(game-task firecanyon-buzzer)))
+(define *hub2-cell-list*
+ (new 'static
+ 'boxed-array
+ :type
+ game-task
+ (game-task training-gimmie)
+ (game-task training-door)
+ (game-task training-climb)
+ (game-task training-buzzer)
+ (game-task village1-mayor-money)
+ (game-task village1-uncle-money)
+ (game-task village1-yakow)
+ (game-task village1-oracle-money1)
+ (game-task village1-oracle-money2)
+ (game-task village1-buzzer)
+ (game-task beach-ecorocks)
+ (game-task beach-flutflut)
+ (game-task beach-pelican)
+ (game-task beach-seagull)
+ (game-task beach-cannon)
+ (game-task beach-gimmie)
+ (game-task beach-sentinel)
+ (game-task beach-buzzer)
+ (game-task jungle-lurkerm)
+ (game-task jungle-tower)
+ (game-task jungle-eggtop)
+ (game-task jungle-plant)
+ (game-task jungle-fishgame)
+ (game-task jungle-canyon-end)
+ (game-task jungle-temple-door)
+ (game-task jungle-buzzer)
+ (game-task misty-muse)
+ (game-task misty-boat)
+ (game-task misty-cannon)
+ (game-task misty-warehouse)
+ (game-task misty-bike)
+ (game-task misty-bike-jump)
+ (game-task misty-eco-challenge)
+ (game-task misty-buzzer)
+ (game-task firecanyon-end)
+ (game-task firecanyon-buzzer)))
+
(define *hub2-cell-list*
(new 'static
'boxed-array
@@ -404,14 +450,20 @@
(defun speedrun-draw-settings ()
"Draw speedrun related settings in the bottom left corner"
(when (and (-> *pc-settings* speedrunner-mode?) (not (paused?)) (-> *speedrun-info* should-display?))
+ ;; check if we've landed (either we're on ground/surface/water, or about to target-hit-ground)
+ (when (and *target*
+ (or (logtest? (-> *target* control status) (cshape-moving-flags onground onsurf on-water))
+ (= (-> *target* next-state name) 'target-hit-ground)))
+ (true! *has-landed?*))
(with-dma-buffer-add-bucket ((buf (-> (current-frame) global-buf)) (bucket-id subtitle))
- (draw-string-xy (string-format "Speedrunner Mode ~%OpenGOAL Version: ~S ~%Category: ~S ~%Cutscene Skips ~A"
- *pc-settings-built-sha*
+ (draw-string-xy (string-format "Speedrunner Mode ~%ModBase ~S / ~S ~%Category: ~S ~%Cutscene Skips ~A~%Has Landed? ~A"
+ *pc-settings-built-sha* *mod-version-text*
(enum->string speedrun-category (-> *speedrun-info* category))
- (-> *pc-settings* skip-movies?))
+ (-> *pc-settings* skip-movies?)
+ *has-landed?*)
buf
0
- (- 220 (* 8 4))
+ (- 220 (* 8 5))
(font-color flat-yellow)
(font-flags shadow kerning))))
(none))
diff --git a/goal_src/jak2/dgos/game.gd b/goal_src/jak2/dgos/game.gd
index 531727c3f8..d680d374d8 100644
--- a/goal_src/jak2/dgos/game.gd
+++ b/goal_src/jak2/dgos/game.gd
@@ -343,6 +343,7 @@
"pckernel.o" ;; added
"subtitle2-h.o" ;; added
"subtitle2.o" ;; added
+ "input-display.o" ;;added ;;mod-base-change
"main.o"
"collide-cache.o"
"collide-debug.o"
@@ -465,4 +466,9 @@
"elec-gate.o"
"cty-guard-turret-button.o"
"entity-debug.o" ;; added
+ "mod-settings.o" ;; added ;;mod-base-change
+ "mod-common-functions.o" ;; added
+ "orb-placer.o" ;; added
+ "mod-custom-code.o" ;; added
+ "mod-debug.o" ;; added
))
diff --git a/goal_src/jak2/engine/common_objs/collectables.gc b/goal_src/jak2/engine/common_objs/collectables.gc
index 21b6745e7f..353c3e08f1 100644
--- a/goal_src/jak2/engine/common_objs/collectables.gc
+++ b/goal_src/jak2/engine/common_objs/collectables.gc
@@ -22,6 +22,8 @@
(declare-type eco collectable)
(declare-type ammo collectable)
+(define-extern orb-placer-list-maintenace (function symbol none))
+
;; DECOMP BEGINS
(defskelgroup skel-health collectables collectables-health-lod0-jg collectables-health-idle-ja
@@ -1491,6 +1493,12 @@ This commonly includes things such as:
:code (behavior ((arg0 symbol) (arg1 handle))
(process-entity-status! self (entity-perm-status dead) #t)
)
+ :exit (behavior ()
+ ;; refresh orb-placer list in debug mode
+ (when *debug-segment*
+ (orb-placer-list-maintenace #t)
+ )
+ )
)
(defmethod initialize-allocations ((this money))
diff --git a/goal_src/jak2/engine/common_objs/crates.gc b/goal_src/jak2/engine/common_objs/crates.gc
index b8b5d2bc4a..42ccc2021a 100644
--- a/goal_src/jak2/engine/common_objs/crates.gc
+++ b/goal_src/jak2/engine/common_objs/crates.gc
@@ -1012,6 +1012,22 @@
)
)
+;;mod-base-change
+(defbehavior collectable-count crate ((arg0 process-tree))
+ (set! *global-search-count* 0)
+ (iterate-process-tree
+ arg0
+ (lambda ((arg0 process))
+ (if (type? arg0 collectable)
+ (set! *global-search-count* (+ *global-search-count* 1))
+ )
+ #t
+ )
+ *null-kernel-context*
+ )
+ *global-search-count*
+ )
+
(defstate special-contents-die (crate)
:virtual #t
:event (behavior ((proc process) (argc int) (message symbol) (block event-message-block))
@@ -1029,6 +1045,7 @@
)
(process-entity-status! self (entity-perm-status dead) #t)
(process-entity-status! self (entity-perm-status subtask-complete) #t)
+ (process-entity-status! self (entity-perm-status bit-15) #f) ;;mod-base-change
(set! (-> gp-0 user-int8 0) 2)
(let ((v0-0 (logclear (-> self mask) (process-mask sleep))))
(set! (-> self mask) v0-0)
@@ -1069,8 +1086,9 @@
0
(logior! (-> self draw status) (draw-control-status no-draw))
(drop-pickup (-> self fact) #t self (the-as fact-info #f) 0)
- (set! (-> self child-count) (+ (process-count self) -1))
+ (set! (-> self child-count) (collectable-count self)) ;;mod-base-change
(process-entity-status! self (entity-perm-status bit-4) #t)
+ (process-entity-status! self (entity-perm-status bit-15) #t) ;;mod-base-change
(when (-> self entity)
(let ((v1-32 (-> self entity extra perm)))
(logior! (-> v1-32 status) (entity-perm-status bit-5))
@@ -1279,7 +1297,9 @@ This commonly includes things such as:
(defmethod crate-method-38 ((this crate))
(when (-> this entity)
- (if (>= (-> this entity extra perm user-int8 0) 1)
+ (if (and (>= (-> this entity extra perm user-int8 0) 1)
+ (logtest? (-> this entity extra perm status) (entity-perm-status bit-15)) ;;mod-base-change
+ )
(go (method-of-object this die) #t 0)
)
)
diff --git a/goal_src/jak2/engine/debug/default-menu.gc b/goal_src/jak2/engine/debug/default-menu.gc
index 728b428cd1..4d924226a3 100644
--- a/goal_src/jak2/engine/debug/default-menu.gc
+++ b/goal_src/jak2/engine/debug/default-menu.gc
@@ -3769,6 +3769,7 @@
arg0
'(menu
"Display"
+ (flag "Input Display" *show-input-display* dm-boolean-toggle-pick-func)
(flag "Profile" *display-profile* dm-boolean-toggle-pick-func)
(flag "Ticks" *profile-ticks* dm-boolean-toggle-pick-func)
(flag "File Info" *display-file-info* dm-boolean-toggle-pick-func)
diff --git a/goal_src/jak2/engine/draw/drawable.gc b/goal_src/jak2/engine/draw/drawable.gc
index f1e3e3ace3..b2964d22f3 100644
--- a/goal_src/jak2/engine/draw/drawable.gc
+++ b/goal_src/jak2/engine/draw/drawable.gc
@@ -109,7 +109,6 @@
)
)
-
(defun guard-band-cull ((arg0 vector))
"Is the given sphere within the guard band, and possibly needs clipping?"
(local-vars (v1-0 uint128) (v1-1 uint128) (v1-2 uint128))
diff --git a/goal_src/jak2/engine/entity/entity-h.gc b/goal_src/jak2/engine/entity/entity-h.gc
index 62a7f15f9f..6cc633cec3 100644
--- a/goal_src/jak2/engine/entity/entity-h.gc
+++ b/goal_src/jak2/engine/entity/entity-h.gc
@@ -21,6 +21,10 @@
(bit-10 10)
(save 11)
(bit-12 12)
+ ;;mod-base-change
+ (bit-13 13)
+ (bit-14 14)
+ (bit-15 15)
)
(defenum vehicle-type
diff --git a/goal_src/jak2/engine/entity/entity.gc b/goal_src/jak2/engine/entity/entity.gc
index 49751acfd8..b578a033cb 100644
--- a/goal_src/jak2/engine/entity/entity.gc
+++ b/goal_src/jak2/engine/entity/entity.gc
@@ -1984,16 +1984,30 @@
(let* ((v1-0 arg0)
(s5-0 (cond
((or (= v1-0 'life) (= v1-0 'debug))
- 4719
+ (entity-perm-status bit-0 error dead no-kill bit-5 subtask-complete bit-9 bit-12 bit-15) ;;mod-base-change
)
((= v1-0 'try)
- 4719
+ (entity-perm-status bit-0 error dead no-kill bit-5 subtask-complete bit-9 bit-12 bit-15) ;;mod-base-change
)
((= v1-0 'game)
- 8063
+ (entity-perm-status bit-0
+ error
+ dead
+ no-kill
+ bit-4
+ bit-5
+ subtask-complete
+ complete
+ bit-9
+ bit-10
+ save
+ bit-12
+ bit-13
+ bit-15 ;;mod-base-change
+ )
)
(else
- 5759
+ (entity-perm-status bit-0 error dead no-kill bit-4 bit-5 subtask-complete bit-9 bit-10 bit-12 bit-15) ;;mod-base-change
)
)
)
diff --git a/goal_src/jak2/engine/game/main.gc b/goal_src/jak2/engine/game/main.gc
index ff21c39ea5..ade79304cc 100644
--- a/goal_src/jak2/engine/game/main.gc
+++ b/goal_src/jak2/engine/game/main.gc
@@ -6,6 +6,7 @@
;; dgos: ENGINE, GAME
;; DECOMP BEGINS
+(defun-extern runs-every-frame (none))
(defun set-letterbox-frames ((arg0 time-frame))
"Set the remaining time until letterboxing is removed.
@@ -865,6 +866,7 @@
(if (-> *level* loading-level)
(load-continue (-> *level* loading-level))
)
+ (runs-every-frame) ;;mod-base-change
; ;; Run blerc to modify foreground models
(with-profiler 'merc *profile-merc-color*
diff --git a/goal_src/jak2/engine/gfx/font.gc b/goal_src/jak2/engine/gfx/font.gc
index 2e2e83c8c2..24d979c62e 100644
--- a/goal_src/jak2/engine/gfx/font.gc
+++ b/goal_src/jak2/engine/gfx/font.gc
@@ -209,7 +209,16 @@
(none)
)
-
-
+;; Added for PC port
+(defun draw-string-xy-scaled ((str string) (buf dma-buffer) (x int) (y int) (color font-color) (flags font-flags) (scale float))
+ "Draw a string at the given xy location, with the given scale."
+ (let ((font-ctxt (new 'stack 'font-context *font-default-matrix* x y 0.0 color flags)))
+ (*! (-> font-ctxt scale) scale)
+ (*! (-> font-ctxt width) scale)
+ (*! (-> font-ctxt height) scale)
+ (draw-string str buf font-ctxt)
+ )
+ (none)
+ )
diff --git a/goal_src/jak2/engine/mods/input-display.gc b/goal_src/jak2/engine/mods/input-display.gc
new file mode 100644
index 0000000000..a23a857083
--- /dev/null
+++ b/goal_src/jak2/engine/mods/input-display.gc
@@ -0,0 +1,376 @@
+;;-*-Lisp-*-
+(in-package goal)
+
+#|
+An input display written in GOAL
+
+Controller joystick locations have text coordinates and a grid display
+
+Face buttons were added to game_custom_text so they could be displayed with lookup-text!
+Other buttons are displayed using text: "R3" "Select"
+
+Sections of the input display are anchored by an -origin vector
+Individual buttons have an -offset vector relative to the origin
+ ie: the face buttons center is (90, 208) on-screen, and the Triangle button is 12 units to the left, and 18 units above
+ try (set-vector! stick-origin 60 20 0 0)
+
+Use L2 + L3 + Dpad to start editing the input display
+The dpad press displays text and changes the edit mode
+Down lets you edit buttons, left lets you edit the sticks, right for dpad, up to stop editing
+|#
+
+;; *show-input-display* is the on/off switch controlling whether this is displayed
+
+;
+;Button stuff
+;
+;Button center coordinates
+(define button-origin (new 'static 'vector4w :x 90 :y 380 :z 0 :w 0))
+;Face buttons
+(define triangle-offset (new 'static 'vector4w :x -12 :y -36 :z 0 :w 0))
+(define x-offset (new 'static 'vector4w :x -12 :y 4 :z 0 :w 0))
+(define circle-offset (new 'static 'vector4w :x 6 :y -16 :z 0 :w 0))
+(define square-offset (new 'static 'vector4w :x -30 :y -16 :z 0 :w 0))
+
+(defun draw-face-buttons ((arg0 dma-buffer))
+ ;Face buttons. Each button needed added to text-h.gc and english texts
+ (when (cpad-hold? 0 triangle)
+ (draw-string-xy (lookup-text! *common-text* (text-id pad-triangle) #f) arg0 (+ (-> button-origin x) (-> triangle-offset x)) (+ (-> button-origin y) (-> triangle-offset y)) (font-color green) (font-flags shadow kerning large)) )
+ (when (cpad-hold? 0 circle)
+ (draw-string-xy (lookup-text! *common-text* (text-id pad-circle) #f) arg0 (+ (-> button-origin x) (-> circle-offset x)) (+ (-> button-origin y) (-> circle-offset y)) (font-color green) (font-flags shadow kerning large)) )
+ (when (cpad-hold? 0 x)
+ (draw-string-xy (lookup-text! *common-text* (text-id pad-x) #f) arg0 (+ (-> button-origin x) (-> x-offset x)) (+ (-> button-origin y) (-> x-offset y)) (font-color green) (font-flags shadow kerning large)) )
+ (when (cpad-hold? 0 square)
+ (draw-string-xy (lookup-text! *common-text* (text-id pad-square) #f) arg0 (+ (-> button-origin x) (-> square-offset x)) (+ (-> button-origin y) (-> square-offset y)) (font-color green) (font-flags shadow kerning large)) )
+)
+
+;
+;Other buttons (LRs, select, start)
+;
+;(define R-x-offset 13)
+;(define L-x-offset (- 28)) ;use a minus for negative numbers
+;(define RL1-y-offset (- 16))
+;(define RL2-y-offset (- 23))
+;(define RL3-y-offset 8)
+
+(define r1-offset (new 'static 'vector4w :x 13 :y -32 :z 0 :w 0))
+(define r2-offset (new 'static 'vector4w :x 13 :y -46 :z 0 :w 0))
+(define r3-offset (new 'static 'vector4w :x 13 :y 16 :z 0 :w 0))
+(define l1-offset (new 'static 'vector4w :x -28 :y -32 :z 0 :w 0))
+(define l2-offset (new 'static 'vector4w :x -28 :y -46 :z 0 :w 0))
+(define l3-offset (new 'static 'vector4w :x -28 :y 16 :z 0 :w 0))
+; select uses l3 offset
+; start uses r3 offset
+
+(defun draw-other-buttons ((arg0 dma-buffer))
+ ;LR/Shoulder buttons
+ (when (cpad-hold? 0 r1)
+ (draw-string-xy "R1" arg0 (+ (-> button-origin x) (-> r1-offset x)) (+ (-> button-origin y) (-> r1-offset y)) (the font-color 13) (font-flags shadow kerning)) )
+ (when (cpad-hold? 0 r2)
+ (draw-string-xy "R2" arg0 (+ (-> button-origin x) (-> r2-offset x)) (+ (-> button-origin y) (-> r2-offset y)) (the font-color 13) (font-flags shadow kerning)) )
+ (when (cpad-hold? 0 r3)
+ (draw-string-xy "R3" arg0 (+ (-> button-origin x) (-> r3-offset x)) (+ (-> button-origin y) (-> r3-offset y)) (the font-color 13) (font-flags shadow kerning)) )
+ (when (cpad-hold? 0 l1)
+ (draw-string-xy "L1" arg0 (+ (-> button-origin x) (-> l1-offset x)) (+ (-> button-origin y) (-> l1-offset y)) (the font-color 13) (font-flags shadow kerning)) )
+ (when (cpad-hold? 0 l2)
+ (draw-string-xy "L2" arg0 (+ (-> button-origin x) (-> l2-offset x)) (+ (-> button-origin y) (-> l2-offset y)) (the font-color 13) (font-flags shadow kerning)) )
+ (when (cpad-hold? 0 l3)
+ (draw-string-xy "L3" arg0 (+ (-> button-origin x) (-> l3-offset x)) (+ (-> button-origin y) (-> l3-offset y)) (the font-color 13) (font-flags shadow kerning)) )
+
+ ;Select/Start, aligned with L3/R3
+ (when (cpad-hold? 0 start)
+ (draw-string-xy "Start" arg0 (+ (-> button-origin x) (-> r3-offset x)) (+ (-> button-origin y) (-> r3-offset y)) (the font-color 13) (font-flags shadow kerning)) )
+ (when (cpad-hold? 0 select)
+ (draw-string-xy "Select" arg0 (+ (-> button-origin x) (-> l3-offset x)) (+ (-> button-origin y) (-> l3-offset y)) (the font-color 13) (font-flags shadow kerning)) )
+
+)
+
+
+
+;
+;D-Pad inputs
+;
+(define dpad-origin (new 'static 'vector4w :x 90 :y 380 :z 0 :w 0))
+(define up-offset (new 'static 'vector4w :x -12 :y -36 :z 0 :w 0))
+(define down-offset (new 'static 'vector4w :x -12 :y 4 :z 0 :w 0))
+(define left-offset (new 'static 'vector4w :x -30 :y -16 :z 0 :w 0))
+(define right-offset (new 'static 'vector4w :x 6 :y -16 :z 0 :w 0))
+
+(defun draw-dpad ((arg0 dma-buffer))
+ (when (cpad-hold? 0 up)
+ (draw-string-xy "up" arg0 (+ (-> dpad-origin x) (-> up-offset x)) (+ (-> dpad-origin y) (-> up-offset y)) (the font-color 13) (font-flags shadow kerning)) )
+ (when (cpad-hold? 0 down)
+ (draw-string-xy "down" arg0 (+ (-> dpad-origin x) (-> down-offset x)) (+ (-> dpad-origin y) (-> down-offset y)) (the font-color 13) (font-flags shadow kerning)) )
+ (when (cpad-hold? 0 left)
+ (draw-string-xy "left" arg0 (+ (-> dpad-origin x) (-> left-offset x)) (+ (-> dpad-origin y) (-> left-offset y)) (the font-color 13) (font-flags shadow kerning)) )
+ (when (cpad-hold? 0 right)
+ (draw-string-xy "right" arg0 (+ (-> dpad-origin x) (-> right-offset x)) (+ (-> dpad-origin y) (-> right-offset y)) (the font-color 13) (font-flags shadow kerning)) )
+)
+
+
+
+;
+;Joystick stuff
+;
+(define show-stick1? #t)
+(define stick-origin (new 'static 'vector4w :x 30 :y 380 :z 0 :w 0))
+(define stick-text-offset (new 'static 'vector4w :x -20 :y -40 :z 0 :w 0))
+
+;no idea how the draw scaling works, this seems good enough
+;length of axes lines, started with 128 and adjusted until it looked good
+(define height-idk (/ 256 5))
+(define width-idk (/ 128 3))
+
+(defun draw-stick ((arg0 dma-buffer))
+ (when show-stick1?
+ ;up/down axis
+ (draw-sprite2d-xy arg0
+ (-> stick-origin x) ;the vertical line's x coordinate = stick center x coordinate
+ (- (-> stick-origin y) (/ height-idk 2)) ;we shift the line up, by half its height, so that the line midpoint = center y coordinate
+ 2
+ height-idk
+ *color-black*)
+ ;left/right axis
+ (draw-sprite2d-xy arg0
+ (- (-> stick-origin x) (/ width-idk 2))
+ (-> stick-origin y)
+ width-idk
+ 1
+ *color-gray*)
+
+ ;Text for stick values
+ (draw-string-xy-scaled (string-format "~d ~d" (-> *cpad-list* cpads 0 leftx) (-> *cpad-list* cpads 0 lefty)) arg0
+ (+ (-> stick-origin x) (-> stick-text-offset x))
+ (+ (-> stick-origin y) (-> stick-text-offset y))
+ (font-color green) (font-flags shadow kerning ) 0.75)
+
+ ;raw stick location, nice for locating screen coordinates
+ ;(draw-sprite2d-xy testbuf (the int (-> *cpad-list* cpads 0 leftx)) (the int(-> *cpad-list* cpads 0 lefty)) 5 3 *color-red*)
+
+ ;stick location scaled to picture
+ (draw-sprite2d-xy arg0
+ ;analog-input takes left stick x int value and maps its min/max to negative/positive of the last argument
+ ;in our case we scale to half of the picture's width, so the stick can go full + (right) or full - (left)
+ (+ (-> stick-origin x)
+ (analog-input (the int (-> *cpad-list* cpads 0 leftx)) 128.0 0.0 128.0 (the float (/ width-idk 2))))
+ ;repeat for y coordinate
+ (+ (-> stick-origin y)
+ (analog-input (the int (-> *cpad-list* cpads 0 lefty)) 128.0 0.0 128.0 (the float (/ height-idk 2))))
+ 3
+ 2
+ *color-cyan*)
+ )
+)
+
+;
+;Second stick
+;
+(define show-stick2? #f)
+(define stick2-origin (new 'static 'vector4w :x 150 :y 380 :z 0 :w 0))
+(define stick2-text-offset (new 'static 'vector4w :x -20 :y -40 :z 0 :w 0))
+
+(defun draw-stick2 ((arg0 dma-buffer))
+ (when show-stick2?
+ ;up/down axis
+ (draw-sprite2d-xy arg0
+ (-> stick2-origin x)
+ (- (-> stick2-origin y) (/ height-idk 2))
+ 2
+ height-idk
+ *color-black*)
+ ;left/right axis
+ (draw-sprite2d-xy arg0
+ (- (-> stick2-origin x) (/ width-idk 2))
+ (-> stick2-origin y)
+ width-idk
+ 1
+ *color-gray*)
+
+ ;Text for stick values
+ (draw-string-xy-scaled (string-format "~d ~d" (-> *cpad-list* cpads 0 rightx) (-> *cpad-list* cpads 0 righty)) arg0
+ (+ (-> stick2-origin x) (-> stick2-text-offset x))
+ (+ (-> stick2-origin y) (-> stick2-text-offset y))
+ (font-color cyan) (font-flags shadow kerning ) 0.75)
+
+ ;stick location scaled to picture
+ (draw-sprite2d-xy arg0
+ (+ (-> stick2-origin x)
+ (analog-input (the int (-> *cpad-list* cpads 0 rightx)) 128.0 0.0 128.0 (the float (/ width-idk 2))))
+ (+ (-> stick2-origin y)
+ (analog-input (the int (-> *cpad-list* cpads 0 righty)) 128.0 0.0 128.0 (the float (/ height-idk 2))))
+ 3
+ 2
+ *color-green*)
+ )
+)
+
+;
+;edit mode
+;
+(define button-edit-mode 0)
+(define button-edit-strings (new 'static 'boxed-array :type string
+ ""
+ "Hold a button and use dpad to move"
+ "Editing Sticks"
+ "Editing Dpad"
+ ))
+
+(defun edit-mode-controls ()
+ ;Hold L2 + L3 + dpad to change the edit mode
+ (when (and (cpad-hold? 0 l2) (cpad-hold? 0 l3))
+ (when (cpad-pressed? 0 up) (set! button-edit-mode 0))
+ (when (cpad-pressed? 0 down) (set! button-edit-mode 1))
+ (when (cpad-pressed? 0 left) (set! button-edit-mode 2))
+ (when (cpad-pressed? 0 right) (set! button-edit-mode 3))
+ )
+
+ ;moves individual face buttons with dpad
+ (when (= button-edit-mode 1)
+ (when (cpad-hold? 0 triangle) ;when you hold triangle, you can move triangle's offset vector with the dpad
+ (when (cpad-hold? 0 up) (+! (-> triangle-offset y) -1))
+ (when (cpad-hold? 0 down) (+! (-> triangle-offset y) 1))
+ (when (cpad-hold? 0 left) (+! (-> triangle-offset x) -1))
+ (when (cpad-hold? 0 right) (+! (-> triangle-offset x) 1))
+ )
+ (when (cpad-hold? 0 circle)
+ (when (cpad-hold? 0 up) (+! (-> circle-offset y) -1))
+ (when (cpad-hold? 0 down) (+! (-> circle-offset y) 1))
+ (when (cpad-hold? 0 left) (+! (-> circle-offset x) -1))
+ (when (cpad-hold? 0 right) (+! (-> circle-offset x) 1))
+ )
+ (when (cpad-hold? 0 x)
+ (when (cpad-hold? 0 up) (+! (-> x-offset y) -1))
+ (when (cpad-hold? 0 down) (+! (-> x-offset y) 1))
+ (when (cpad-hold? 0 left) (+! (-> x-offset x) -1))
+ (when (cpad-hold? 0 right) (+! (-> x-offset x) 1))
+ )
+ (when (cpad-hold? 0 square)
+ (when (cpad-hold? 0 up) (+! (-> square-offset y) -1))
+ (when (cpad-hold? 0 down) (+! (-> square-offset y) 1))
+ (when (cpad-hold? 0 left) (+! (-> square-offset x) -1))
+ (when (cpad-hold? 0 right) (+! (-> square-offset x) 1))
+ )
+ ;LR buttons
+ (when (cpad-hold? 0 r1)
+ (when (cpad-hold? 0 up) (+! (-> r1-offset y) -1))
+ (when (cpad-hold? 0 down) (+! (-> r1-offset y) 1))
+ (when (cpad-hold? 0 left) (+! (-> r1-offset x) -1))
+ (when (cpad-hold? 0 right) (+! (-> r1-offset x) 1))
+ )
+ (when (cpad-hold? 0 r2)
+ (when (cpad-hold? 0 up) (+! (-> r2-offset y) -1))
+ (when (cpad-hold? 0 down) (+! (-> r2-offset y) 1))
+ (when (cpad-hold? 0 left) (+! (-> r2-offset x) -1))
+ (when (cpad-hold? 0 right) (+! (-> r2-offset x) 1))
+ )
+ (when (or (cpad-hold? 0 r3) (cpad-hold? 0 start)) ;use an or here since start/r3 share coords
+ (when (cpad-hold? 0 up) (+! (-> r3-offset y) -1))
+ (when (cpad-hold? 0 down) (+! (-> r3-offset y) 1))
+ (when (cpad-hold? 0 left) (+! (-> r3-offset x) -1))
+ (when (cpad-hold? 0 right) (+! (-> r3-offset x) 1))
+ )
+ (when (cpad-hold? 0 l1)
+ (when (cpad-hold? 0 up) (+! (-> l1-offset y) -1))
+ (when (cpad-hold? 0 down) (+! (-> l1-offset y) 1))
+ (when (cpad-hold? 0 left) (+! (-> l1-offset x) -1))
+ (when (cpad-hold? 0 right) (+! (-> l1-offset x) 1))
+ )
+ (when (cpad-hold? 0 l2)
+ (when (cpad-hold? 0 up) (+! (-> l2-offset y) -1))
+ (when (cpad-hold? 0 down) (+! (-> l2-offset y) 1))
+ (when (cpad-hold? 0 left) (+! (-> l2-offset x) -1))
+ (when (cpad-hold? 0 right) (+! (-> l2-offset x) 1))
+ )
+ (when (or (cpad-hold? 0 l3) (cpad-hold? 0 select)) ;use an or here since select/l3 share coords
+ (when (cpad-hold? 0 up) (+! (-> l3-offset y) -1))
+ (when (cpad-hold? 0 down) (+! (-> l3-offset y) 1))
+ (when (cpad-hold? 0 left) (+! (-> l3-offset x) -1))
+ (when (cpad-hold? 0 right) (+! (-> l3-offset x) 1))
+ )
+ )
+ ;moves joysticks
+ (when (= button-edit-mode 2)
+ ;stick1 with dpad
+ (when (cpad-hold? 0 up) (+! (-> stick-origin y) -1))
+ (when (cpad-hold? 0 down) (+! (-> stick-origin y) 1))
+ (when (cpad-hold? 0 left) (+! (-> stick-origin x) -1))
+ (when (cpad-hold? 0 right) (+! (-> stick-origin x) 1))
+ (when (cpad-pressed? 0 l3) (not! show-stick1?))
+ ;stick2 with face buttons
+ (when (cpad-hold? 0 triangle) (+! (-> stick2-origin y) -1))
+ (when (cpad-hold? 0 x) (+! (-> stick2-origin y) 1))
+ (when (cpad-hold? 0 square) (+! (-> stick2-origin x) -1))
+ (when (cpad-hold? 0 circle) (+! (-> stick2-origin x) 1))
+ (when (cpad-pressed? 0 r3) (not! show-stick2?))
+ )
+ ;moves dpad
+ (when (= button-edit-mode 3)
+ ;dpad-origin with dpad
+ (when (cpad-hold? 0 up) (+! (-> dpad-origin y) -1))
+ (when (cpad-hold? 0 down) (+! (-> dpad-origin y) 1))
+ (when (cpad-hold? 0 left) (+! (-> dpad-origin x) -1))
+ (when (cpad-hold? 0 right) (+! (-> dpad-origin x) 1))
+ )
+)
+
+
+
+;
+;
+;this is the main function/process
+;
+;
+
+(defun input-display-on ()
+ (when (not (process-by-name "button-proc" *active-pool*))
+ ;process-spawn-function, spawns a process that runs the function you give it
+ (process-spawn-function process :name "button-proc"
+ ;This lambda is our function
+ (lambda :behavior process ()
+ ;Code before the loop runs once on process spawn
+ ;
+ ;Loop runs once per frame while process is active
+ (loop
+
+ ;function that checks input to turn on editing
+ (edit-mode-controls)
+
+ ;Start a bucket thing block so we can use draw functions
+ (with-dma-buffer-add-bucket ((testbuf (-> (current-frame) global-buf)) (bucket-id debug-no-zbuf1))
+ ;Edit mode string draws
+ (when (and (cpad-hold? 0 l2) (cpad-hold? 0 l3))
+ (draw-string-xy-scaled "L2+L3+Dpad to start editing input display" testbuf 5 170 (the font-color 12) (font-flags shadow kerning) 0.7) )
+ (when (!= button-edit-mode 0)
+ (draw-string-xy-scaled (string-format "~s" (-> button-edit-strings button-edit-mode)) testbuf 5 177 (the font-color 12) (font-flags shadow kerning) 0.7) )
+
+ ;Other draw stuff
+ (draw-face-buttons testbuf)
+
+ (draw-other-buttons testbuf)
+
+ (draw-dpad testbuf)
+
+ (draw-stick testbuf)
+
+ (draw-stick2 testbuf)
+ )
+
+ ;Processes should suspend themselves, the loop will resume next frame
+ (suspend)
+ )
+ )
+ )
+ )
+
+ ;Lisp returns the last form as the function return
+ (none)
+ )
+
+
+(defun input-display-off ()
+ "Kill the button process"
+ (when (process-by-name "button-proc" *active-pool*)
+ (kill-by-name "button-proc" *active-pool*)
+ )
+ (none)
+ )
\ No newline at end of file
diff --git a/goal_src/jak2/engine/mods/mod-common-functions.gc b/goal_src/jak2/engine/mods/mod-common-functions.gc
new file mode 100644
index 0000000000..e739f4da80
--- /dev/null
+++ b/goal_src/jak2/engine/mods/mod-common-functions.gc
@@ -0,0 +1,365 @@
+;;-*-Lisp-*-
+(in-package goal)
+
+;; name: mod-common-functions.gc
+;; name in dgo: mod-common-functions
+;; dgos: TODO
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; What is this file for.
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+#| This file is a place where you can define custom functions and GOAL code
+ to call inside of mod-custom-code.gc for example I have defined a function that increases
+ the powercell count by one when it is called
+ |#
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Useful GOAL modding documentation
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+#|
+Checks the condition and if it is true it does first argument if false it does optional second argument
+(if (condition) (do if true) (do if false))
+
+Gives a random FLOAT or INT between the provided ranges when called
+(rand-vu-float-range 0.0 2.0)
+(rand-vu-int-range 0 10)
+
+if the result of rand-vu-int-range is 1, then DANCE! if it is not 1, then Don't dance
+(if (= (rand-vu-int-range 0 10) 1) (DANCE!) (Don't dance))
+
+
+|#
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Define Custom Variables to use in mods
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(define *target-board-hold-tricks* 0) ;; used to detect if player is doing a jetboard hover (see board-states.gc)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Define Custom Functions to call in mods
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; add your own unique custom functions here!
+
+;; Macros can be used more-or-less just like functions
+(defmacro current-cell-count ()
+ `(-> *game-info* fuel)
+ )
+
+(defmacro set-current-cell-count (count)
+ `(set! (-> *game-info* fuel) ,count)
+ )
+
+(defun increase-power-cell-by-one ()
+ (set-current-cell-count (+ (current-cell-count) 1))
+ ;; with the two macros defined above, this is equivalent to
+ ;; (set! (-> *game-info* fuel) (+ (-> *game-info* fuel) 1))
+ (none)
+ )
+
+(defmacro board-hovering? ()
+ `(and (logtest? (focus-status board) (-> *target* focus-status)) (> *target-board-hold-tricks* 1))
+ )
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Define Approved Custom Functions/Macros to call in all mods
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; These are included with the mod base and you are welcome to use them in your mods!
+
+(defmacro current-checkpoint-name ()
+ `(-> *game-info* current-continue name)
+ )
+
+;;Might want to add #f as a customizable function arg later
+(defun set-current-checkpoint-by-name ((name string))
+ (set-continue! *game-info* name #f)
+ )
+
+(defmacro current-level-name ()
+ `(-> (level-get-target-inside *level*) name)
+ )
+
+(defmacro current-orb-count ()
+ `(-> *game-info* money)
+ )
+
+(defmacro current-cutscene ()
+ `(-> *art-control* active-stream)
+ )
+
+;;This function moves an actor to the given coordinates
+;;example (move-actor farmer-3 3.0 74.0 -120.0)
+(defun move-actor ((actor-name string) (x float) (y float) (z float))
+ (when (entity-by-name actor-name)
+ (let* ((entity-actor (entity-by-name actor-name))
+ (actor (-> entity-actor extra process))
+ )
+ (when actor
+ (case (-> actor type)
+ ((skill)
+
+ (set-vector! (-> entity-actor trans) (meters x) (meters y) (meters z) 1.0)
+ (set-vector! (-> entity-actor extra trans) (meters x) (meters y) (meters z) 1.0)
+ (set-vector! (-> (the process-drawable actor) root trans) (meters x) (meters y) (meters z) 1.0)
+ (set-vector! (-> (the money actor) base) (meters x) (meters y) (meters z) 1.0)
+
+
+
+ )
+ ((crate)
+ ;; only move crates if they're not jumping
+ (when (= (-> (the crate actor) smush amp) 0.0)
+ (set-vector! (-> entity-actor trans) (meters x) (meters y) (meters z) 1.0)
+ (set-vector! (-> entity-actor extra trans) (meters x) (meters y) (meters z) 1.0)
+ (set-vector! (-> (the process-drawable actor) root trans) (meters x) (meters y) (meters z) 1.0)
+ (set-vector! (-> (the crate actor) base) (meters x) (meters y) (meters z) 1.0)
+ ;; (set-vector! (-> (the crate actor) root trans) (meters x) (meters y) (meters z) 1.0)
+ ;; (set! (-> (the crate actor) root root-prim world-sphere x) (meters x))
+ ;; (set! (-> (the crate actor) root root-prim world-sphere y) (meters y))
+ ;; (set! (-> (the crate actor) root root-prim world-sphere z) (meters z))
+ )
+ )
+
+ (else
+ (format 0 "unexpected actor type ~S ~S ~S~%" actor-name (-> entity-actor type) (-> actor type))
+ (set-vector! (-> entity-actor trans) (meters x) (meters y) (meters z) 1.0)
+ (set-vector! (-> entity-actor extra trans) (meters x) (meters y) (meters z) 1.0)
+ ;; (set-vector! (-> (the process-drawable actor) root trans) (meters x) (meters y) (meters z) 1.0)
+ )
+ )
+ )
+ )
+ )
+ (none)
+ )
+
+;;Draws a debug sphere on the actor, takes in a string actor name and a radius for the sphere in meters
+(defun draw-debug-sphere-on-actor ((actorName string)(radius float))
+ (when *debug-segment*
+ (when (process-by-ename actorName)
+ (add-debug-sphere #t (bucket-id bucket-0) (-> (the-as process-drawable (process-by-ename actorName)) root trans) (meters radius) (static-rgba 0 #xff 0 #x40))
+ )
+ )
+ (none)
+)
+
+
+;;This function moves a given actor to jaks current position, then prints a (move-actors) call in gk.exe
+(defun move-to-jak ((arg0 string))
+(format #t "move-actor code: (move-actor ~a ~m ~m ~m)~%" arg0(-> (target-pos 0) x) (-> (target-pos 0) y) (-> (target-pos 0) z))
+ (when (process-by-ename arg0)
+ (set-vector! (-> (-> (the process-drawable (process-by-ename arg0))root)trans) (-> (target-pos 0) x) (-> (target-pos 0) y) (-> (target-pos 0) z) 1.0)
+ (if (type-type? (-> (process-by-ename arg0) type) crate)
+ (begin
+ (set! (-> (the crate (process-by-ename arg0)) base y) (-> (target-pos 0) y))
+ )
+ (none)
+ )
+
+ (if (type-type? (-> (process-by-ename arg0) type) money)
+ (begin
+ (set! (-> (the money (process-by-ename arg0)) base y) (-> (target-pos 0) y))
+ )
+ (none)
+ )
+
+
+ )
+)
+
+;; quick macro for setting vector xyz in meters, leaving w alone
+(defmacro set-vector-meters! (dst x y z)
+ `(set-vector! ,dst (meters ,x) (meters ,y) (meters ,z) (-> ,dst w))
+ )
+
+;; quick macro for constructing static vector with w=1
+(defmacro static-vector-meters (x y z)
+ `(new 'static 'vector :x (meters ,x) :y (meters ,y) :z (meters ,z) :w 1.0)
+ )
+
+;; prints vector xyz in meters
+(defmacro print-vector-meters (vec &key (dst #t))
+ `(format ,dst "~m ~m ~m~%" (-> ,vec x) (-> ,vec y) (-> ,vec z))
+ )
+
+;; takes a path-control and xyz values to offsets every node in the path by
+(defmacro shift-path! (path x y z)
+ `(let ((voff (static-vector-meters ,x ,y ,z)))
+ (dotimes (idx (-> ,path num-cverts))
+ (vector+! (-> ,path cverts idx) (-> ,path cverts idx) voff)
+ )
+ )
+ )
+
+;; prints all the nodes in a path in meters
+(defmacro path-print-meters (path)
+ `(dotimes (idx (-> ,path num-cverts))
+ (print-vector-meters (-> ,path cverts idx))
+ )
+ )
+
+;; prints the position (root trans) of a process-drawable
+(defmacro pd-pos-m (procname)
+ `(let* ((obj (the process-drawable (process-by-ename ,procname)))
+ (vec (-> obj root trans)))
+ (format 0 "~m ~m ~m~%" (-> vec x) (-> vec y) (-> vec z))
+ (none)
+ )
+ )
+
+;;This function moves an actor based on jaks position + an offset
+(defun move-to-behind-jak ((arg0 string) (arg1 meters) (arg2 meters))
+ (when (process-by-ename arg0)
+ (set-vector! (-> (-> (the process-drawable (process-by-ename arg0))root)trans) (-(-> (target-pos 0) x) (meters arg1)) (+ (-> (target-pos 0) y) (meters arg2)) (-(-> (target-pos 0) z)(meters arg1)) 1.0)
+ (if (type-type? (-> (process-by-ename arg0) type) money)
+ (begin
+ (set! (-> (the money (process-by-ename arg0)) base y) (-> (target-pos 0) y) )
+ )
+ (none)
+ )
+
+ )
+)
+
+
+
+;; ;;This turns on collision render when called
+;; (defun turnonCollisionmode()
+;; (set! *collision-renderer* #t)
+;; (logclear! *vu1-enable-user-menu* (vu1-renderer-mask tfrag trans-tfrag tie tie-near))
+;; )
+
+;; ;;This turns off collision render when called
+;; (defun turnoffCollisionmode()
+;; (set! *collision-renderer* #f)
+;; (logior! *vu1-enable-user-menu* (vu1-renderer-mask tfrag trans-tfrag tie tie-near))
+;; )
+
+;; ;;This makes it thunder in the current level
+;; (defun thunderTime()
+;; (set! (-> (level-get-target-inside *level*) mood-func)update-mood-village2)
+;; (set! (-> (level-get-target-inside *level*) mood) *village2-mood*)
+;; )
+
+;; ;;This makes the current level dark when called
+;; (defun DarkesetGlitchTime()
+;; (set! (-> (level-get-target-inside *level*) mood-func)update-mood-finalboss )
+;; (set! (-> (level-get-target-inside *level*) mood) *finalboss-mood*)
+;; )
+
+;; ;;This needs fixed
+;; (defun rainyTime()
+;; (set! (-> (level-get-target-inside *level*) mood-func)update-mood-swamp)
+;; (set! (-> (level-get-target-inside *level*) mood) *swamp-mood*)
+;; )
+
+
+;; ;;This makes the current levels weather the same as village1
+;; (defun defaultWeatherTime()
+;; (set! (-> (level-get-target-inside *level*) mood-func)update-mood-village1)
+;; (set! (-> (level-get-target-inside *level*) mood) *village1-mood*)
+;; )
+
+;;This moves jak to a provided coordinate example call
+;;(tp-jak 0.0 12.0 32.32)
+(defun tp-jak ((arg0 float)(arg1 float)(arg2 float))
+ (set! (-> (target-pos 0) x) (meters arg0))
+ (set! (-> (target-pos 0) y) (meters arg1))
+ (set! (-> (target-pos 0) z) (meters arg2))
+)
+
+;;This returns true or false depending on if jak is within a provided distance from an actor
+(defun close? ((actor-ename string) (dist float))
+ (and
+ (process-by-ename actor-ename)
+ (<=
+ (vector-vector-distance
+ (target-pos 0)
+ (-> (the process-drawable (process-by-ename actor-ename)) root trans)
+ )
+ dist
+ )
+ )
+ )
+
+
+;;This returns true or false if jak is within a bubble defined by coordiantes and width
+(defun in-bubble? ((x float) (y float) (z float) (w float))
+ (<=
+ (vector-vector-distance
+ (target-pos 0)
+ (set-vector! (new-stack-vector0) x y z 1.0)
+ )
+ (/ w 2.0)
+ )
+ )
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Jak Color functions
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defun draw-xyz ((jak target) (x float) (y float) (z float))
+ (set! (-> jak draw color-mult x) x)
+ (set! (-> jak draw color-mult y) y)
+ (set! (-> jak draw color-mult z) z)
+)
+(defun draw-normal ((jak target))
+ (draw-xyz jak 1.0 1.0 1.0)
+)
+(defun draw-white ((jak target))
+ (draw-xyz jak 10.0 10.0 10.0)
+)
+(defun draw-black ((jak target))
+ (draw-xyz jak 0.0 0.0 0.0)
+)
+(defun draw-red ((jak target))
+ (draw-xyz jak 3.0 0.0 0.0)
+)
+(defun draw-green ((jak target))
+ (draw-xyz jak 0.0 3.0 0.0)
+)
+(defun draw-blue ((jak target))
+ (draw-xyz jak 0.0 0.0 3.0)
+)
+(defun draw-yellow ((jak target))
+ (draw-xyz jak 3.0 3.0 0.0)
+)
+(defun draw-pink ((jak target))
+ (draw-xyz jak 3.0 0.0 3.0)
+)
+(defun draw-light-blue ((jak target))
+ (draw-xyz jak 0.0 3.0 3.0)
+)
+
+;; Helper functions for spawning orbs (used by orb placer in debug mode)
+
+(defun spawn-skill ((vec vector) (amount float) (bob? symbol))
+ (let ((fax (new 'static 'fact-info)))
+ (set! (-> fax pickup-type) (pickup-type skill))
+ (set! (-> fax pickup-amount) amount)
+ (set! (-> fax pickup-spawn-amount) amount)
+ ;; make sure it doesn't timeout and disappear
+ (logior! (-> fax options) (actor-option fade-out))
+ (set! (-> fax fade-time) (the-as time-frame 0))
+
+ (let ((proc (the skill (ppointer->process (birth-pickup-at-point vec (pickup-type skill) amount #t *active-pool* fax)))))
+ (when bob?
+ (set! (-> proc bob-amount) 1024.0)
+ )
+ (format 0 "spawned ~A~%" proc)
+ ;; return handle to the orb
+ (process->handle proc)
+ )
+ )
+ )
+
+(defun spawn-skill-meters ((x float) (y float) (z float) (amount float) (bob? symbol))
+ (let ((vec (new 'stack-no-clear 'vector)))
+ (set-vector-meters! vec x y z)
+ (spawn-skill vec amount bob?)
+ )
+ )
\ No newline at end of file
diff --git a/goal_src/jak2/engine/mods/mod-custom-code.gc b/goal_src/jak2/engine/mods/mod-custom-code.gc
new file mode 100644
index 0000000000..9aea3501e6
--- /dev/null
+++ b/goal_src/jak2/engine/mods/mod-custom-code.gc
@@ -0,0 +1,119 @@
+ ;;-*-Lisp-*-
+ (in-package goal)
+
+ ;; name: mod-custom-code.gc
+ ;; name in dgo: mod-custom-code
+ ;; dgos: TODO
+
+
+ ;;;;;;;;;;;;;;;;;;;;;;;;;;
+ ;; What is this file for.
+ ;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+ #| This file contains function defenitions that are pre placed in the mod base,
+ so if you place custom code inside of these functions, it will exectue based on
+ the name of the function, for example, if you place (set! (-> *game-info* fuel) (+ (-> *game-info* fuel) 1))
+ to the function named runs-on-orb-pickup, then jaks powercell count will increase each time you collect
+ an orb |#
+
+
+ ;;;;;;;;;;;;;;;;;;;;;;;;;;
+ ;; Begin function defintions.
+ ;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+ (defun runs-every-frame ()
+ ;; (increase-power-cell-by-one) This is a call to increase-power-cell-by-one defined in mod-common-functions.gc
+
+ ;; Sample code to turn jak pink whenever he's doing the jetboard hover glitch
+ ;; (when *target*
+ ;; (if (board-hovering?)
+ ;; (draw-pink *target*)
+ ;; (draw-normal *target*)
+ ;; )
+ ;; )
+
+ (if *show-input-display*
+ (input-display-on)
+ (input-display-off)
+ )
+
+ (when *debug-segment*
+ (orb-placer-maintenance)
+ )
+
+ (none)
+ )
+
+ (defun runs-on-gem-pickup ()
+ ;; Code here runs on any scout fly pickup
+
+ (none)
+ )
+
+ (defun runs-on-task-close ()
+
+
+ (none)
+ )
+
+ (defun runs-on-eco-pickup ((eco-type pickup-type) (parent process-tree))
+ (let* ((from-vent? #f))
+ ;; Code here runs as soon as you pickup ANY eco
+
+ (case eco-type
+ (((pickup-type eco-yellow))
+ ;; Code here runs as soon as you pickup yellow eco
+
+ )
+ (((pickup-type eco-red))
+ ;; Code here runs as soon as you pickup red eco
+
+ )
+ (((pickup-type eco-blue))
+ ;; Code here runs as soon as you pickup blue eco
+
+ )
+
+ (((pickup-type eco-green))
+ ;; Code here runs as soon as you pickup big green eco
+
+ )
+ )
+
+ (when from-vent?
+ ;; Code here runs only if the eco was picked up from a vent
+
+ )
+ )
+
+ (none)
+ )
+
+ (defun runs-on-jak-spawn ()
+ ;; Code here runs every time jak spawns (loading a file new game or death)
+
+ (none)
+ )
+
+ (defun runs-on-jak-death ((death-event symbol))
+ (case death-event
+ (('dying)
+ ;; Code here runs immediately every time jak dies, before any death animation or death cutscene
+
+ )
+ (('blackout)
+ ;; Code here runs after jak dies (and any death cutscene finishes), during the blackout before he spawns
+
+ )
+ )
+
+ (none)
+ )
+
+
+ ;;;;;;;;;;;;;;;;;;;;;;;;;;
+ ;; deprecated function defintions.
+ ;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+ #| these are no longer recommended/supported however we include them anyways to not break anyones mods.
+ |#
diff --git a/goal_src/jak2/engine/mods/mod-debug.gc b/goal_src/jak2/engine/mods/mod-debug.gc
new file mode 100644
index 0000000000..feec7bb609
--- /dev/null
+++ b/goal_src/jak2/engine/mods/mod-debug.gc
@@ -0,0 +1,34 @@
+;;-*-Lisp-*-
+(in-package goal)
+
+;; For debug-only additions to the mod-base. Anything defined in this file will be unavailable in retail mode.
+(declare-file (debug))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Additional debug menu(s)
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defun-debug debug-menu-make-modding-tools-menu ((ctx debug-menu-context))
+ (let ((modding-tools-menu (new 'debug 'debug-menu ctx "Modding Tools")))
+
+ ;; orb-placer menu
+ (let ((orb-placer-menu (new 'debug 'debug-menu ctx "Orb Placer")))
+ (debug-menu-append-item orb-placer-menu (new-dm-bool "Edit Mode?" *orb-placer-enabled?* dm-boolean-toggle-pick-func))
+ (debug-menu-append-item orb-placer-menu (new-dm-func "Add Orb" #f orb-placer-add))
+ (let ((select-orb-menu (new 'debug 'debug-menu ctx "Select Orb")))
+ (set! *orb-placer-select-menu* select-orb-menu)
+ ;; populated on orb add
+ (debug-menu-append-item orb-placer-menu (new-dm-submenu "Select Orb" select-orb-menu))
+ )
+ (debug-menu-append-item orb-placer-menu (new-dm-func "Print Selected Orb Position" #f orb-placer-print-selected))
+ (debug-menu-append-item orb-placer-menu (new-dm-func "Print All Orb Positions" #f orb-placer-print-all))
+
+ (debug-menu-append-item modding-tools-menu (new-dm-submenu "Orb Placer" orb-placer-menu))
+ )
+ (new-dm-submenu "Modding Tools" modding-tools-menu)
+ )
+ )
+
+(when (-> *debug-menu-context* root-menu)
+ (debug-menu-append-item (-> *debug-menu-context* root-menu) (debug-menu-make-modding-tools-menu *debug-menu-context*))
+ )
\ No newline at end of file
diff --git a/goal_src/jak2/engine/mods/mod-settings.gc b/goal_src/jak2/engine/mods/mod-settings.gc
new file mode 100644
index 0000000000..58f3548ade
--- /dev/null
+++ b/goal_src/jak2/engine/mods/mod-settings.gc
@@ -0,0 +1,50 @@
+;;-*-Lisp-*-
+(in-package goal)
+
+;; name: mod-common-functions.gc
+;; name in dgo: mod-common-functions
+;; dgos: TODO
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; What is this file for.
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+#| This file is a place where you can define custom functions and GOAL code
+ to call inside of mod-custom-code.gc for example I have defined a function that increases
+ the powercell count by one when it is called
+ |#
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Useful GOAL modding documentation
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+#|
+Checks the condition and if it is true it does first argument if false it does optional second argument
+(if (condition) (do if true) (do if false))
+
+Gives a random FLOAT or INT between the provided ranges when called
+(rand-vu-float-range 0.0 2.0)
+(rand-vu-int-range 0 10)
+
+if the result of rand-vu-int-range is 1, then DANCE! if it is not 1, then Don't dance
+(if (= (rand-vu-int-range 0 10) 1) (DANCE!) (Don't dance))
+
+
+|#
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Define Settings to use in mods
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Define Custom Settings Variables to use in mods
+;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;; Change #f to #t here to show the input display by default
+(define *show-input-display* #f)
+
+;; do NOT change %MODVERSIONPLACEHOLDER% below, otherwise the mod-bundling-tools
+;; will be unable to automatically add version info to the speedrun display
+(define *mod-version-text* "%MODVERSIONPLACEHOLDER%")
\ No newline at end of file
diff --git a/goal_src/jak2/engine/mods/orb-placer.gc b/goal_src/jak2/engine/mods/orb-placer.gc
new file mode 100644
index 0000000000..0e25353aec
--- /dev/null
+++ b/goal_src/jak2/engine/mods/orb-placer.gc
@@ -0,0 +1,405 @@
+(in-package goal)
+
+(declare-file (debug))
+
+;; controls whether the orb-placer process will run
+(define *orb-placer-enabled?* #f)
+
+;; number of orbs orb-placer has placed
+(define *orb-placer-count* 0)
+
+;; max number of orbs
+(defconstant ORB_PLACER_MAX 100)
+
+;; index of currently selected orb for placing
+(define *orb-placer-selected-idx* -1)
+
+;; array of handles to orbs that have been spawned by orb-placer
+(define *orb-placer-orbs* (new 'global 'boxed-array handle ORB_PLACER_MAX))
+
+(define *orb-placer-temp-strs* (new 'static 'boxed-array :type string :length ORB_PLACER_MAX
+ "orba-00"
+ "orba-01"
+ "orba-02"
+ "orba-03"
+ "orba-04"
+ "orba-05"
+ "orba-06"
+ "orba-07"
+ "orba-08"
+ "orba-09"
+ "orba-10"
+ "orba-11"
+ "orba-12"
+ "orba-13"
+ "orba-14"
+ "orba-15"
+ "orba-16"
+ "orba-17"
+ "orba-18"
+ "orba-19"
+ "orba-20"
+ "orba-21"
+ "orba-22"
+ "orba-23"
+ "orba-24"
+ "orba-25"
+ "orba-26"
+ "orba-27"
+ "orba-28"
+ "orba-29"
+ "orba-30"
+ "orba-31"
+ "orba-32"
+ "orba-33"
+ "orba-34"
+ "orba-35"
+ "orba-36"
+ "orba-37"
+ "orba-38"
+ "orba-39"
+ "orba-40"
+ "orba-41"
+ "orba-42"
+ "orba-43"
+ "orba-44"
+ "orba-45"
+ "orba-46"
+ "orba-47"
+ "orba-48"
+ "orba-49"
+ "orba-50"
+ "orba-51"
+ "orba-52"
+ "orba-53"
+ "orba-54"
+ "orba-55"
+ "orba-56"
+ "orba-57"
+ "orba-58"
+ "orba-59"
+ "orba-60"
+ "orba-61"
+ "orba-62"
+ "orba-63"
+ "orba-64"
+ "orba-65"
+ "orba-66"
+ "orba-67"
+ "orba-68"
+ "orba-69"
+ "orba-70"
+ "orba-71"
+ "orba-72"
+ "orba-73"
+ "orba-74"
+ "orba-75"
+ "orba-76"
+ "orba-77"
+ "orba-78"
+ "orba-79"
+ "orba-80"
+ "orba-81"
+ "orba-82"
+ "orba-83"
+ "orba-84"
+ "orba-85"
+ "orba-86"
+ "orba-87"
+ "orba-88"
+ "orba-89"
+ "orba-90"
+ "orba-91"
+ "orba-92"
+ "orba-93"
+ "orba-94"
+ "orba-95"
+ "orba-96"
+ "orba-97"
+ "orba-98"
+ "orba-99"
+ ))
+
+;; global for this so we can repopulate it as needed
+(define *orb-placer-select-menu* (the-as debug-menu #f))
+
+(defun dm-orb-placer-select-func ((idx int) (msg debug-menu-msg))
+ (when (= msg (debug-menu-msg press))
+ (cond
+ ((= *orb-placer-selected-idx* idx)
+ ;; deselect
+ (set! *orb-placer-selected-idx* -1)
+ (set! *orb-placer-enabled?* #f)
+ )
+ (else
+ ;; select
+ (set! *orb-placer-selected-idx* idx)
+ (set! *orb-placer-enabled?* #t)
+ )
+ )
+ )
+ (= *orb-placer-selected-idx* idx)
+ )
+
+(defun orb-placer-highlight ((orb skill) (highlight? symbol))
+ (cond
+ (highlight?
+ (set-vector! (-> orb draw color-mult) 0.8 0.8 0.0 1.0)
+ (set-vector! (-> orb draw color-emissive) 0.0 1.0 0.2 1.0)
+ )
+ (else
+ (set-vector! (-> orb draw color-mult) 0.8 0.8 0.8 1.0)
+ (set-vector! (-> orb draw color-emissive) 0.2 0.2 0.2 1.0)
+ )
+ )
+ (none)
+ )
+
+(defun orb-placer-list-maintenace ((update-debug-list? symbol))
+ (when update-debug-list? (debug-menu-remove-all-items *orb-placer-select-menu*))
+
+ (dotimes (i *orb-placer-count*)
+ (let ((orb-handle (-> *orb-placer-orbs* i))
+ (is-selected? (and *orb-placer-enabled?* (= i *orb-placer-selected-idx*))))
+ (when (and (nonzero? orb-handle) (handle->process orb-handle))
+ (let ((orb-proc (the skill (handle->process orb-handle))))
+ (when (and orb-proc (!= (-> orb-proc next-state name) 'dead-state))
+ ;; ensure correct highlighting
+ (orb-placer-highlight
+ orb-proc
+ is-selected?
+ )
+
+ ;; draw z-debug text
+ (add-debug-text-3d
+ #t
+ (bucket-id debug-no-zbuf1)
+ (-> *orb-placer-temp-strs* i)
+ (-> orb-proc base)
+ (if is-selected? (font-color red) (font-color white))
+ (new 'static 'vector2h :y 16)
+ )
+
+ (when update-debug-list?
+ ;; append to debug menu list
+ (let ((orb-menu-item (new-dm-flag (-> *orb-placer-temp-strs* i) i dm-orb-placer-select-func)))
+ (debug-menu-append-item *orb-placer-select-menu* orb-menu-item)
+ (when is-selected?
+ (set! (-> *orb-placer-select-menu* selected-item) orb-menu-item)
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+
+ (when update-debug-list?
+ (set! (-> *orb-placer-select-menu* items) (sort (-> *orb-placer-select-menu* items) debug-menu-node))
+ )
+ (none)
+ )
+
+(defun orb-placer-add ()
+ (when (< *orb-placer-count* ORB_PLACER_MAX)
+ (let ((vec (new 'stack-no-clear 'vector))
+ (camera-quat (new-stack-quaternion0))
+ (camera-z-vector (new-stack-vector0)))
+ ;; figure out spawn position
+ (cond
+ (*target*
+ ;; jak exists, use his position
+ (vector-copy! vec (-> *target* root trans))
+ (+! (-> vec y) (meters 2.0)) ;; dont spawn in ground
+ )
+ (else
+ ;; use camera position
+ (vector-copy! vec (-> *math-camera* trans))
+ )
+ )
+ ;; convert the camera angle from a matrix to a quaternion (???)
+ (matrix->quaternion camera-quat (-> *camera-combiner* inv-camera-rot))
+ ;; convert the quaternion to a vector representing rotation around z axis (isnt it the y axis in GOAL tho?)
+ (vector-z-quaternion! camera-z-vector camera-quat)
+ ;; shift orb's position with camera angle, by 3m
+ (vector+! vec vec (vector-normalize! camera-z-vector (meters 3.0)))
+
+ ;; spawn and update orb-placer data
+ (let ((orb-handle (spawn-skill vec 1.0 #t)))
+ (when (nonzero? orb-handle)
+ (set! (-> *orb-placer-orbs* *orb-placer-count*) orb-handle)
+ (set! *orb-placer-selected-idx* *orb-placer-count*)
+ (orb-placer-highlight (the skill (handle->process orb-handle)) #t)
+ (+! *orb-placer-count* 1)
+ (orb-placer-list-maintenace #t)
+ )
+ )
+ )
+ )
+ (none)
+ )
+
+(defun orb-placer-print-selected ()
+ (when (and (>= *orb-placer-selected-idx* 0) (nonzero? (-> *orb-placer-orbs* *orb-placer-selected-idx*)))
+ (let ((orb (the skill (handle->process (-> *orb-placer-orbs* *orb-placer-selected-idx*)))))
+ (when orb
+ (format 0 " ~m, ~m, ~m~%" (-> orb base x) (-> orb base y) (-> orb base z))
+ )
+ )
+ )
+ (none)
+ )
+
+(defun orb-placer-print-all ()
+ (format 0 "|------------orba start------------|~%")
+ (dotimes (i *orb-placer-count*)
+ (let ((orb-handle (-> *orb-placer-orbs* i)))
+ (when (and (nonzero? orb-handle) (handle->process orb-handle))
+ (let ((orb (the skill (handle->process orb-handle))))
+ (format 0 " ~m, ~m, ~m~%" (-> orb base x) (-> orb base y) (-> orb base z))
+ )
+ )
+ )
+ )
+ (format 0 "|-------------orba end-------------|~%")
+ (none)
+ )
+
+;; called from run-every-frame to ensure orb-placer is spawned/killed
+(define *orb-offset-tmp-vec* (new 'global 'vector))
+(defun orb-placer-maintenance ()
+ (when *debug-segment*
+ (orb-placer-list-maintenace #f)
+
+ (when (and (not (paused?)) *orb-placer-enabled?*)
+ (with-dma-buffer-add-bucket ((buf (-> (current-frame) global-buf))
+ (bucket-id debug-no-zbuf1))
+ (let ((font-ctx (new 'stack 'font-context *font-default-matrix* 510 2 0.0 (font-color red) (font-flags right shadow kerning large))))
+ (set! (-> font-ctx scale) 0.325)
+ (draw-string-adv "Orb Placer Edit Mode" buf font-ctx)
+ )
+ )
+ )
+
+
+ (cond
+ ((process-by-name "orb-placer" *active-pool*)
+ ;; orb-placer exists
+ ;; if its not enabled, kill it
+ (when (not (and *debug-segment* *orb-placer-enabled?*))
+
+ (kill-by-name "orb-placer" *active-pool*)
+
+ ;; make sure target is ungrabbed
+ (when (and *target* (= (-> *target* next-state name) 'target-grab))
+ (send-event *target* 'end-mode)
+ )
+ ;; release any camera modes
+ (when (setting-control-method-14 *setting-control* 'mode-name)
+ (remove-setting-by-arg0 *setting-control* 'mode-name)
+ )
+ )
+ )
+ (else
+ ;; orb-placer doesn't exist
+ ;; if its enabled, spawn it
+ (when (and *debug-segment* *orb-placer-enabled?*)
+ ;; process-spawn-function, spawns a process that runs the function you give it
+ (process-spawn-function process :name "orb-placer"
+ (lambda :behavior process ()
+ (stack-size-set! (-> self top-thread) 512)
+ ;; Code before the loop runs once on process spawn
+
+ (let ((pad (-> *cpad-list* cpads 0))
+ (vec *orb-offset-tmp-vec*))
+ (loop
+ ;; Loop runs once per frame while process is active
+
+ ;; make sure target is grabbed
+ (when (and *target* (!= (-> *target* next-state name) 'target-grab))
+ (send-event *target* 'change-mode 'grab)
+ )
+ ;; lock freecam if no target
+ (when (and (not *target*) (not (send-event *camera* 'query-state cam-fixed)))
+ (set-setting-by-param *setting-control* 'mode-name 'cam-fixed 0 0)
+ )
+
+ ;; if we have an orb selected and the handle is nonzero...
+ (when (and (>= *orb-placer-selected-idx* 0) (nonzero? (-> *orb-placer-orbs* *orb-placer-selected-idx*)))
+ (let ((orb (the skill (handle->process (-> *orb-placer-orbs* *orb-placer-selected-idx*)))))
+ (when orb
+ ;; highlight it
+ (orb-placer-highlight orb #t)
+
+ ;; respond to controller input
+
+ ;; X/Z based on camera
+ (when (nonzero? (-> pad stick0-speed))
+ (set! (-> vec x) (sin (-> pad stick0-dir)))
+ (set! (-> vec y) 0.0)
+ (set! (-> vec z) (cos (-> pad stick0-dir)))
+ (set! (-> vec w) 0.0)
+ ;; camera magic
+ (vector-matrix*! vec vec (matrix-local->world #t #f))
+ (vector-flatten! vec vec (-> *camera* local-down))
+ (vector-float*! vec vec (* (-> pad stick0-speed) 512.0))
+
+ ;; actually move orb
+ (vector+! (-> orb root trans) (-> orb root trans) vec)
+ (vector+! (-> orb base) (-> orb base) vec)
+ )
+
+
+ (cond
+ ;; fine tune/axis-aligned X/Z
+ ((cpad-pressed? 0 down)
+ (+! (-> orb root trans z) (meters 0.03))
+ (+! (-> orb base z) (meters 0.03))
+ )
+ ((cpad-pressed? 0 up)
+ (+! (-> orb root trans z) (meters -0.03))
+ (+! (-> orb base z) (meters -0.03))
+ )
+ ((cpad-pressed? 0 right)
+ (+! (-> orb root trans x) (meters 0.03))
+ (+! (-> orb base x) (meters 0.03))
+ )
+ ((cpad-pressed? 0 left)
+ (+! (-> orb root trans x) (meters -0.03))
+ (+! (-> orb base x) (meters -0.03))
+ )
+ ;; Y (up/down)
+ ((cpad-hold? 0 r2)
+ (+! (-> orb root trans y) (meters 0.08))
+ (+! (-> orb base y) (meters 0.08))
+ )
+ ((cpad-hold? 0 l2)
+ (+! (-> orb root trans y) (meters -0.08))
+ (+! (-> orb base y) (meters -0.08))
+ )
+ ((cpad-pressed? 0 r1)
+ (+! (-> orb root trans y) (meters 0.03))
+ (+! (-> orb base y) (meters 0.03))
+ )
+ ((cpad-pressed? 0 l1)
+ (+! (-> orb root trans y) (meters -0.03))
+ (+! (-> orb base y) (meters -0.03))
+ )
+ )
+ )
+ )
+ )
+
+ ;; Processes should suspend themselves, the loop will resume next frame
+ (suspend)
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ )
+ (none)
+ )
\ No newline at end of file
diff --git a/goal_src/jak2/engine/target/board/board-states.gc b/goal_src/jak2/engine/target/board/board-states.gc
index 019aeab673..27aaa78e75 100644
--- a/goal_src/jak2/engine/target/board/board-states.gc
+++ b/goal_src/jak2/engine/target/board/board-states.gc
@@ -25,6 +25,8 @@
(define-extern target-hit-orient (function attack-info vector symbol :behavior target))
(define-extern target-hit-move (function attack-info symbol (function none :behavior target) float none :behavior target))
+(define-extern *target-board-hold-tricks* int)
+
(declare-type grenade projectile)
;; DECOMP BEGINS
@@ -120,6 +122,7 @@
(none)
)
+
;; WARN: Return type mismatch object vs none.
(defbehavior target-board-spin-check target ()
(when (and (or (cpad-pressed? (-> self control cpad number) r1)
@@ -245,6 +248,7 @@
)
)
(set! (-> self board unknown-float01) f28-1)
+ (+! *target-board-hold-tricks* 1)
(go
target-board-hold
(-> *TARGET_BOARD-bank* tricky-jump-height-min)
@@ -1665,6 +1669,9 @@
(set! (-> self control dynam gravity-length) 147456.0)
)
:exit (behavior ()
+ (when (and (-> self next-state) (!= 'target-board-hold (-> self next-state name)))
+ (set! *target-board-hold-tricks* 0)
+ )
(set! (-> self board unknown-float01) 0.0)
(set-time! (-> self board unknown-time-frame05))
(let ((v1-3 (the-as sound-rpc-set-param (get-sound-buffer-entry))))
diff --git a/goal_src/jak2/engine/target/board/target-board.gc b/goal_src/jak2/engine/target/board/target-board.gc
index d969759d7c..f22b11c8d1 100644
--- a/goal_src/jak2/engine/target/board/target-board.gc
+++ b/goal_src/jak2/engine/target/board/target-board.gc
@@ -31,6 +31,9 @@
(define-extern target-board-halfpipe-check (function collide-action :behavior target))
(define-extern target-board-jump-trans (function none :behavior target))
+(define-extern *target-board-hold-tricks* int)
+
+
(#when PC_PORT
(define *board-trick-tracker* (new 'static 'board-trick-tracker))
@@ -2935,6 +2938,7 @@
(end-combo! *board-trick-tracker*))
)
)
+ (set! *target-board-hold-tricks* 0)
(set! (-> this trick-count) 0)
0
0
diff --git a/goal_src/jak2/engine/ui/text-id-h.gc b/goal_src/jak2/engine/ui/text-id-h.gc
index e2c9ca245d..519c880377 100644
--- a/goal_src/jak2/engine/ui/text-id-h.gc
+++ b/goal_src/jak2/engine/ui/text-id-h.gc
@@ -828,7 +828,13 @@
(progress-graphics-ps2-lod-dist #x1337)
(progress-graphics-msaa-off #x1338)
(progress-graphics-display #x1339)
- (language-name-finnish #x133a)
+ (language-name-finnish #x133a)
+
+ ;; mod text
+ (pad-triangle #x2000)
+ (pad-circle #x2001)
+ (pad-x #x2002)
+ (pad-square #x2003)
)
;; ---text-id
diff --git a/goal_src/jak2/kernel-defs.gc b/goal_src/jak2/kernel-defs.gc
index e8199d990d..7d3a356678 100644
--- a/goal_src/jak2/kernel-defs.gc
+++ b/goal_src/jak2/kernel-defs.gc
@@ -238,6 +238,20 @@
(define-extern pc-discord-rpc-update (function discord-info none))
(define-extern pc-discord-rpc-set (function int none))
(define-extern pc-init-autosplitter-struct (function none))
+
+;;main music start
+(define-extern play-sound-file (function string int none))
+(define-extern play-main-music (function string int none))
+(define-extern pause-main-music (function none))
+(define-extern stop-main-music (function none))
+(define-extern resume-main-music (function none))
+(define-extern main-music-volume (function int none))
+;;main music end
+
+(define-extern play-sound-file (function string int none))
+(define-extern stop-sound-file (function string none))
+(define-extern stop-all-sounds (function none))
+
(define-extern pc-filepath-exists? (function string symbol))
(define-extern pc-mkdir-file-path (function string none))
(define-extern pc-sound-set-flava-hack (function int none))
diff --git a/goal_src/jak2/pc/features/speedruns.gc b/goal_src/jak2/pc/features/speedruns.gc
index ebefbf7ade..68aaf801cd 100644
--- a/goal_src/jak2/pc/features/speedruns.gc
+++ b/goal_src/jak2/pc/features/speedruns.gc
@@ -3,6 +3,8 @@
;; TODO later - customize menu open keybind
+(define-extern *mod-version-text* string)
+
(define-extern task-close! (function string symbol))
(define *speedrun-info* (new 'static 'speedrun-info))
@@ -428,21 +430,21 @@
((= (-> this category) (speedrun-category custom))
(pc-sr-mode-get-custom-category-name (-> this active-custom-category index) *pc-cpp-temp-string*)
(format *temp-string*
- "Category: ~S~%Secrets: ~D~%Features: ~D~%Forbidden Features: ~D~%Cheats: ~D~%Version: ~S~%"
+ "Category: ~S~%Secrets: ~D~%Features: ~D~%Forbidden Features: ~D~%Cheats: ~D~%Mod Version: ~S~%"
*pc-cpp-temp-string*
(-> this active-custom-category secrets)
(-> this active-custom-category features)
(-> this active-custom-category forbidden-features)
(-> this active-custom-category pc-cheats)
- *pc-settings-built-sha*))
+ *mod-version-text*))
(else
(format *temp-string*
- "Category: ~S~%PC Cheats: ~D~%Frame Rate: ~D~%PS2 Actor Vis?: ~S~%Version: ~S~%"
+ "Category: ~S~%PC Cheats: ~D~%Frame Rate: ~D~%PS2 Actor Vis?: ~S~%Mod Version: ~S~%"
(enum->string speedrun-category (-> this category))
(the-as int (-> *pc-settings* cheats))
(-> *pc-settings* target-fps)
(if (-> *pc-settings* ps2-actor-vis?) "true" "false")
- *pc-settings-built-sha*)))
+ *mod-version-text*)))
(pc-encode-utf8-string *temp-string* *pc-encoded-temp-string*)
(with-dma-buffer-add-bucket ((buf (-> (current-frame) global-buf)) (bucket-id debug-no-zbuf2))
;; reset bucket settings prior to drawing - font won't do this for us, and
diff --git a/goal_src/jak2/pc/pckernel-impl.gc b/goal_src/jak2/pc/pckernel-impl.gc
index bbaf5b584e..4977b161c6 100644
--- a/goal_src/jak2/pc/pckernel-impl.gc
+++ b/goal_src/jak2/pc/pckernel-impl.gc
@@ -44,7 +44,6 @@
(weather-good)
(hijack-lines)
)
-
;; pc enum for languages. this is the game's languages + custom ones.
(defenum pc-language
:type uint16
diff --git a/goal_src/jak3/kernel-defs.gc b/goal_src/jak3/kernel-defs.gc
index 3b9e958ef8..60728650ce 100644
--- a/goal_src/jak3/kernel-defs.gc
+++ b/goal_src/jak3/kernel-defs.gc
@@ -226,6 +226,18 @@
(declare-type discord-info structure)
(define-extern pc-discord-rpc-update (function discord-info none))
(define-extern pc-discord-rpc-set (function int none))
+
+;;main music start
+(define-extern play-main-music (function string int none))
+(define-extern pause-main-music (function none))
+(define-extern stop-main-music (function none))
+(define-extern resume-main-music (function none))
+(define-extern main-music-volume (function int none))
+;;main music end
+
+(define-extern play-sound-file (function string int none))
+(define-extern stop-sound-file (function string none))
+(define-extern stop-all-sounds (function none))
(define-extern pc-init-autosplitter-struct (function none))
(define-extern pc-filepath-exists? (function string symbol))
(define-extern pc-mkdir-file-path (function string none))
diff --git a/goal_src/user/.gitignore b/goal_src/user/.gitignore
index 69a84b7145..e69de29bb2 100644
--- a/goal_src/user/.gitignore
+++ b/goal_src/user/.gitignore
@@ -1,3 +0,0 @@
-*
-!.gitignore
-!readme.md
diff --git a/goal_src/user/zed/repl-config.json b/goal_src/user/zed/repl-config.json
new file mode 100644
index 0000000000..373e52b9af
--- /dev/null
+++ b/goal_src/user/zed/repl-config.json
@@ -0,0 +1,13 @@
+{
+ "numConnectToTargetAttempts": 1,
+ "jak1": {
+ "asmFileSearchDirs": [
+ "goal_src/jak1"
+ ]
+ },
+ "jak2": {
+ "asmFileSearchDirs": [
+ "goal_src/jak2"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/goalc/build_level/ambient.h b/goalc/build_level/ambient.h
new file mode 100644
index 0000000000..8128f906f2
--- /dev/null
+++ b/goalc/build_level/ambient.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include
+
+class DataObjectGenerator;
+
+struct DrawableTreeAmbient {
+ static size_t add_to_object_file(DataObjectGenerator& gen, size_t ambient_array);
+};
\ No newline at end of file
diff --git a/out/build/Release/bin/decompiler.exe b/out/build/Release/bin/decompiler.exe
new file mode 100644
index 0000000000..9b00dd0596
Binary files /dev/null and b/out/build/Release/bin/decompiler.exe differ
diff --git a/out/build/Release/bin/extractor.exe b/out/build/Release/bin/extractor.exe
new file mode 100644
index 0000000000..1002ff84e6
Binary files /dev/null and b/out/build/Release/bin/extractor.exe differ
diff --git a/out/build/Release/bin/gk.exe b/out/build/Release/bin/gk.exe
new file mode 100644
index 0000000000..db8b870618
Binary files /dev/null and b/out/build/Release/bin/gk.exe differ
diff --git a/out/build/Release/bin/goalc.exe b/out/build/Release/bin/goalc.exe
new file mode 100644
index 0000000000..e479469950
Binary files /dev/null and b/out/build/Release/bin/goalc.exe differ
diff --git a/scripts/batch/makereleasetemplate.bat b/scripts/batch/makereleasetemplate.bat
new file mode 100644
index 0000000000..4e173469bf
--- /dev/null
+++ b/scripts/batch/makereleasetemplate.bat
@@ -0,0 +1,22 @@
+@echo off
+set releaseDir=%cd%\ReleaseTemplate
+
+echo Creating release directory %releaseDir% ...
+if not exist %releaseDir% mkdir %releaseDir%
+
+echo Copying gk.exe, extractor.exe, and goalc.exe ...
+copy "..\..\out\build\Release\bin\gk.exe" "%releaseDir%\"
+copy "..\..\out\build\Release\bin\extractor.exe" "%releaseDir%\"
+copy "..\..\out\build\Release\bin\goalc.exe" "%releaseDir%\"
+xcopy "..\..\out\build\Release\bin\SND\" "%releaseDir%\SND\"
+
+echo Copying data folder ...
+mkdir "%releaseDir%\data\game"
+xcopy /E /I "..\..\goal_src" "%releaseDir%\data\goal_src\"
+xcopy /E /I "..\..\custom_levels" "%releaseDir%\data\custom_levels\"
+xcopy /E /I "..\..\decompiler\config" "%releaseDir%\data\decompiler\config\"
+xcopy /E /I "..\..\game\" "%releaseDir%\data\game\"
+xcopy /E /I "..\..\graphics" "%releaseDir%\data\graphics\"
+
+
+echo Done!
diff --git a/scripts/batch/mod_base/extract-jak1_us2.bat b/scripts/batch/mod_base/extract-jak1_us2.bat
new file mode 100644
index 0000000000..877e31f0ef
--- /dev/null
+++ b/scripts/batch/mod_base/extract-jak1_us2.bat
@@ -0,0 +1,6 @@
+@echo off
+
+task set-game-jak1
+
+task extract
+pause
diff --git a/scripts/batch/mod_base/extract-jak2.bat b/scripts/batch/mod_base/extract-jak2.bat
new file mode 100644
index 0000000000..a98314b03b
--- /dev/null
+++ b/scripts/batch/mod_base/extract-jak2.bat
@@ -0,0 +1,4 @@
+@echo off
+task set-game-jak2
+task extract
+pause
diff --git a/scripts/batch/mod_base/gc.bat b/scripts/batch/mod_base/gc.bat
new file mode 100644
index 0000000000..31892d9552
--- /dev/null
+++ b/scripts/batch/mod_base/gc.bat
@@ -0,0 +1,3 @@
+@echo off
+out\build\Release\bin\goalc --user-auto
+pause
diff --git a/scripts/batch/mod_base/gc2.bat b/scripts/batch/mod_base/gc2.bat
new file mode 100644
index 0000000000..d46f0e0c42
--- /dev/null
+++ b/scripts/batch/mod_base/gc2.bat
@@ -0,0 +1,3 @@
+@echo off
+out\build\Release\bin\goalc --user-auto --game jak2 --auto-mi-exit true
+pause
diff --git a/scripts/batch/mod_base/gk-display.bat b/scripts/batch/mod_base/gk-display.bat
new file mode 100644
index 0000000000..914c16bbad
--- /dev/null
+++ b/scripts/batch/mod_base/gk-display.bat
@@ -0,0 +1,3 @@
+@echo off
+out\build\Release\bin\gk -v -- -boot -fakeiso -debug
+pause
diff --git a/scripts/batch/mod_base/gk2-display.bat b/scripts/batch/mod_base/gk2-display.bat
new file mode 100644
index 0000000000..c9180495fe
--- /dev/null
+++ b/scripts/batch/mod_base/gk2-display.bat
@@ -0,0 +1,5 @@
+@echo off
+task set-game-jak2
+task extract
+out\build\Release\bin\gk -v --game jak2 -- -boot -fakeiso -debug
+pause
diff --git a/scripts/batch/mod_base/jak2.bat b/scripts/batch/mod_base/jak2.bat
new file mode 100644
index 0000000000..22eb2dd1f7
--- /dev/null
+++ b/scripts/batch/mod_base/jak2.bat
@@ -0,0 +1,6 @@
+@echo off
+task set-game-jak2
+task extract
+out\build\Release\bin\goalc --user-auto --game jak2 --auto-mi-exit true
+out\build\Release\bin\gk -v --game jak2 -- -boot -fakeiso -debug
+pause
diff --git a/scripts/bundle-linux.py b/scripts/bundle-linux.py
new file mode 100644
index 0000000000..2b1e5590f1
--- /dev/null
+++ b/scripts/bundle-linux.py
@@ -0,0 +1,110 @@
+import os
+import shutil
+import requests
+import urllib.request
+import zipfile
+
+args = {
+ "outputDir": os.getenv("outputDir"),
+ "versionName": os.getenv("versionName"),
+ "toolingVersion": os.getenv("toolingVersion"),
+ "toolingBinaryDir": os.getenv("toolingBinaryDir"),
+ "textureReplacementDir": os.getenv("textureReplacementDir"),
+ "customLevelsDir": os.getenv("customLevelsDir"),
+ "goalSourceDir": os.getenv("goalSourceDir")
+}
+
+# Create our output directory
+if os.path.exists(os.path.join(args.outputDir, "linux")):
+ print(
+ "Expected output directory already exists, clearing it - {}".format(
+ os.path.join(args.outputDir, "linux")
+ )
+ )
+ os.rmdir(os.path.join(args.outputDir, "linux"))
+
+os.makedirs(os.path.join(args.outputDir, "linux"), exist_ok=True)
+
+# Download the Release
+toolingVersion = args.toolingVersion
+if toolingVersion == "latest":
+ # Get the latest open-goal/jak-project release
+ toolingVersion = requests.get(
+ "https://api.github.com/repos/open-goal/jak-project/releases/latest"
+ ).json()["tag_name"]
+releaseAssetUrl = "https://github.com/open-goal/jak-project/releases/download/{}/opengoal-linux-{}.tar.gz".format(
+ toolingVersion, toolingVersion
+)
+urllib.request.urlretrieve(
+ releaseAssetUrl, os.path.join(args.outputDir, "linux", "release.tar.gz")
+)
+
+# Extract it
+with zipfile.ZipFile(
+ os.path.join(args.outputDir, "linux", "release.tar.gz"), "r"
+) as zip_ref:
+ zip_ref.extractall(os.path.join(args.outputDir, "linux"))
+os.remove(os.path.join(args.outputDir, "linux", "release.tar.gz"))
+
+
+if args.toolingBinaryDir != "":
+ # User is specifying the binaries themselves, let's make sure they exist
+ dir = args.toolingBinaryDir
+ if (
+ not os.path.exists(os.path.join(dir, "extractor"))
+ or not os.path.exists(os.path.join(dir, "goalc"))
+ or not os.path.exists(os.path.join(dir, "gk"))
+ ):
+ print(
+ "Tooling binaries not found, expecting extractor, goalc, and gk"
+ )
+ exit(1)
+ # Binaries are all there, let's replace 'em
+ shutil.copyfile(
+ os.path.join(dir, "extractor"),
+ os.path.join(args.outputDir, "linux", "extractor"),
+ )
+ shutil.copyfile(
+ os.path.join(dir, "goalc"),
+ os.path.join(args.outputDir, "linux", "goalc"),
+ )
+ shutil.copyfile(
+ os.path.join(dir, "gk"), os.path.join(args.outputDir, "linux", "gk")
+ )
+
+# Copy-in Mod Assets
+textureReplacementDir = args.textureReplacementDir
+shutil.copytree(
+ textureReplacementDir,
+ os.path.join(args.outputDir, "linux", "data", "texture_replacements"),
+ dirs_exist_ok=True,
+)
+
+customLevelsDir = args.customLevelsDir
+shutil.copytree(
+ customLevelsDir,
+ os.path.join(args.outputDir, "linux", "data", "custom_levels"),
+ dirs_exist_ok=True,
+)
+
+goalSourceDir = args.goalSourceDir
+shutil.copytree(
+ goalSourceDir,
+ os.path.join(args.outputDir, "linux", "data", "goal_src"),
+ dirs_exist_ok=True,
+)
+
+# Rezip it up and prepare it for upload
+shutil.make_archive(
+ "linux-{}".format(args.versionName),
+ "gztar",
+ os.path.join(args.outputDir, "linux"),
+)
+os.makedirs(os.path.join(args.outputDir, "dist"), exist_ok=True)
+shutil.move(
+ "linux-{}.tar.gz".format(args.versionName),
+ os.path.join(args.outputDir, "dist", "linux-{}.tar.gz".format(args.versionName)),
+)
+
+# Cleanup
+shutil.rmtree(os.path.join(args.outputDir, "linux"))
diff --git a/scripts/bundle-windows.py b/scripts/bundle-windows.py
new file mode 100644
index 0000000000..4ef7cdad1d
--- /dev/null
+++ b/scripts/bundle-windows.py
@@ -0,0 +1,110 @@
+import os
+import shutil
+import requests
+import urllib.request
+import zipfile
+
+args = {
+ "outputDir": os.getenv("outputDir"),
+ "versionName": os.getenv("versionName"),
+ "toolingVersion": os.getenv("toolingVersion"),
+ "toolingBinaryDir": os.getenv("toolingBinaryDir"),
+ "textureReplacementDir": os.getenv("textureReplacementDir"),
+ "customLevelsDir": os.getenv("customLevelsDir"),
+ "goalSourceDir": os.getenv("goalSourceDir")
+}
+
+# Create our output directory
+if os.path.exists(os.path.join(args.outputDir, "windows")):
+ print(
+ "Expected output directory already exists, clearing it - {}".format(
+ os.path.join(args.outputDir, "windows")
+ )
+ )
+ os.rmdir(os.path.join(args.outputDir, "windows"))
+
+os.makedirs(os.path.join(args.outputDir, "windows"), exist_ok=True)
+
+# Download the Release
+toolingVersion = args.toolingVersion
+if toolingVersion == "latest":
+ # Get the latest open-goal/jak-project release
+ toolingVersion = requests.get(
+ "https://api.github.com/repos/open-goal/jak-project/releases/latest"
+ ).json()["tag_name"]
+releaseAssetUrl = "https://github.com/open-goal/jak-project/releases/download/{}/opengoal-windows-{}.zip".format(
+ toolingVersion, toolingVersion
+)
+urllib.request.urlretrieve(
+ releaseAssetUrl, os.path.join(args.outputDir, "windows", "release.zip")
+)
+
+# Extract it
+with zipfile.ZipFile(
+ os.path.join(args.outputDir, "windows", "release.zip"), "r"
+) as zip_ref:
+ zip_ref.extractall(os.path.join(args.outputDir, "windows"))
+os.remove(os.path.join(args.outputDir, "windows", "release.zip"))
+
+
+if args.toolingBinaryDir != "":
+ # User is specifying the binaries themselves, let's make sure they exist
+ dir = args.toolingBinaryDir
+ if (
+ not os.path.exists(os.path.join(dir, "extractor.exe"))
+ or not os.path.exists(os.path.join(dir, "goalc.exe"))
+ or not os.path.exists(os.path.join(dir, "gk.exe"))
+ ):
+ print(
+ "Tooling binaries not found, expecting extractor.exe, goalc.exe, and gk.exe"
+ )
+ exit(1)
+ # Binaries are all there, let's replace 'em
+ shutil.copyfile(
+ os.path.join(dir, "extractor.exe"),
+ os.path.join(args.outputDir, "windows", "extractor.exe"),
+ )
+ shutil.copyfile(
+ os.path.join(dir, "goalc.exe"),
+ os.path.join(args.outputDir, "windows", "goalc.exe"),
+ )
+ shutil.copyfile(
+ os.path.join(dir, "gk.exe"), os.path.join(args.outputDir, "windows", "gk.exe")
+ )
+
+# Copy-in Mod Assets
+textureReplacementDir = args.textureReplacementDir
+shutil.copytree(
+ textureReplacementDir,
+ os.path.join(args.outputDir, "windows", "data", "texture_replacements"),
+ dirs_exist_ok=True,
+)
+
+customLevelsDir = args.customLevelsDir
+shutil.copytree(
+ customLevelsDir,
+ os.path.join(args.outputDir, "windows", "data", "custom_levels"),
+ dirs_exist_ok=True,
+)
+
+goalSourceDir = args.goalSourceDir
+shutil.copytree(
+ goalSourceDir,
+ os.path.join(args.outputDir, "windows", "data", "goal_src"),
+ dirs_exist_ok=True,
+)
+
+# Rezip it up and prepare it for upload
+shutil.make_archive(
+ "windows-{}".format(args.versionName),
+ "zip",
+ os.path.join(args.outputDir, "windows"),
+)
+os.makedirs(os.path.join(args.outputDir, "dist"), exist_ok=True)
+shutil.move(
+ "windows-{}.zip".format(args.versionName),
+ os.path.join(args.outputDir, "dist", "windows-{}.zip".format(args.versionName)),
+)
+
+# Cleanup
+shutil.rmtree(os.path.join(args.outputDir, "windows"))
diff --git a/scripts/shell/decomp3.sh b/scripts/shell/decomp3.sh
old mode 100755
new mode 100644
diff --git a/test/decompiler/reference/jak2/engine/gfx/mood/weather-part_REF.gc b/test/decompiler/reference/jak2/engine/gfx/mood/weather-part_REF.gc
index f499b9807a..1ad699eb14 100644
--- a/test/decompiler/reference/jak2/engine/gfx/mood/weather-part_REF.gc
+++ b/test/decompiler/reference/jak2/engine/gfx/mood/weather-part_REF.gc
@@ -891,3 +891,7 @@
(:func 'sparticle-track-sun)
)
)
+
+
+
+
diff --git a/test/decompiler/reference/jak3/engine/collide/collide-hash-h_REF.gc b/test/decompiler/reference/jak3/engine/collide/collide-hash-h_REF.gc
new file mode 100644
index 0000000000..b8dbb3224d
--- /dev/null
+++ b/test/decompiler/reference/jak3/engine/collide/collide-hash-h_REF.gc
@@ -0,0 +1,267 @@
+;;-*-Lisp-*-
+(in-package goal)
+
+;; definition for symbol *collide-list-boxes*, type symbol
+(define *collide-list-boxes* #f)
+
+;; definition for symbol *collide-hash-fragments*, type int
+(define *collide-hash-fragments* 0)
+
+;; definition for symbol *collide-hash-fragments-tfrag*, type int
+(define *collide-hash-fragments-tfrag* 0)
+
+;; definition for symbol *collide-hash-fragments-instance*, type int
+(define *collide-hash-fragments-instance* 0)
+
+;; definition for symbol *already-printed-exeeded-max-cache-tris*, type symbol
+(define *already-printed-exeeded-max-cache-tris* #f)
+
+;; definition of type collide-hash-scratch
+(deftype collide-hash-scratch (structure)
+ "Scratchpad memory layout for collide-hash. Bitmask of things that have already been checked"
+ ((collidable-bits uint128 128)
+ (poly-bits uint64 2 :overlay-at (-> collidable-bits 0))
+ (id-bits uint32 512 :overlay-at (-> collidable-bits 0))
+ (tris uint32)
+ )
+ )
+
+;; definition for method 3 of type collide-hash-scratch
+(defmethod inspect ((this collide-hash-scratch))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'collide-hash-scratch)
+ (format #t "~1Tcollidable-bits[128] @ #x~X~%" (-> this collidable-bits))
+ (format #t "~1Tpoly-bits[2] @ #x~X~%" (-> this collidable-bits))
+ (format #t "~1Tid-bits[512] @ #x~X~%" (-> this collidable-bits))
+ (format #t "~1Ttris: ~D~%" (-> this tris))
+ (label cfg-4)
+ this
+ )
+
+;; definition of type collide-hash-bucket
+(deftype collide-hash-bucket (structure)
+ "A bucket is a reference to a list of items that intersect a grid cell.
+For the broadphase, the items are collide-hash-item (wrapper of collide-hash-fragment).
+For the narrowphase, the items are entries in the index list, which contains poly indices."
+ ((index int16)
+ (count int16)
+ )
+ )
+
+;; definition for method 3 of type collide-hash-bucket
+(defmethod inspect ((this collide-hash-bucket))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'collide-hash-bucket)
+ (format #t "~1Tindex: ~D~%" (-> this index))
+ (format #t "~1Tcount: ~D~%" (-> this count))
+ (label cfg-4)
+ this
+ )
+
+;; definition of type collide-hash-item
+(deftype collide-hash-item (structure)
+ "Items that are 'hashed' in the broadphase. Contains unique ID for checking against already-visited-bitmask
+and a pointer to the actual collide-hash-fragment, or possibly a TIE."
+ ((id uint32)
+ (collidable basic)
+ )
+ )
+
+;; definition for method 3 of type collide-hash-item
+(defmethod inspect ((this collide-hash-item))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'collide-hash-item)
+ (format #t "~1Tid: ~D~%" (-> this id))
+ (format #t "~1Tcollidable: ~A~%" (-> this collidable))
+ (label cfg-4)
+ this
+ )
+
+;; definition of type collide-hash-poly
+(deftype collide-hash-poly (structure)
+ "A polygon in the narrow-phase data. This is just indices into the vertex and PAT tables."
+ ((data uint8 4)
+ (vert-index0 uint8 :overlay-at (-> data 0))
+ (vert-index1 uint8 :overlay-at (-> data 1))
+ (vert-index2 uint8 :overlay-at (-> data 2))
+ (pat-index uint8 :overlay-at (-> data 3))
+ (word uint32 :overlay-at (-> data 0))
+ )
+ )
+
+;; definition for method 3 of type collide-hash-poly
+(defmethod inspect ((this collide-hash-poly))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'collide-hash-poly)
+ (format #t "~1Tdata[4] @ #x~X~%" (-> this data))
+ (format #t "~1Tvert-index0: ~D~%" (-> this vert-index0))
+ (format #t "~1Tvert-index1: ~D~%" (-> this vert-index1))
+ (format #t "~1Tvert-index2: ~D~%" (-> this vert-index2))
+ (format #t "~1Tpat-index: ~D~%" (-> this pat-index))
+ (format #t "~1Tword: ~D~%" (-> this word))
+ (label cfg-4)
+ this
+ )
+
+;; definition of type collide-hash-fragment-stats
+(deftype collide-hash-fragment-stats (structure)
+ ((num-verts uint16)
+ (num-polys uint8)
+ (poly-count uint8)
+ )
+ :pack-me
+ )
+
+;; definition for method 3 of type collide-hash-fragment-stats
+(defmethod inspect ((this collide-hash-fragment-stats))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'collide-hash-fragment-stats)
+ (format #t "~1Tnum-verts: ~D~%" (-> this num-verts))
+ (format #t "~1Tnum-polys: ~D~%" (-> this num-polys))
+ (format #t "~1Tpoly-count: ~D~%" (-> this poly-count))
+ (label cfg-4)
+ this
+ )
+
+;; definition of type collide-hash-fragment
+(deftype collide-hash-fragment (drawable)
+ "A mesh fragment for the Jak2/Jak3 collision system. This is a 'hash' of triangles into a grid
+where the 'hash' function is just identity."
+ ((num-buckets uint16 :overlay-at id)
+ (num-indices uint16 :offset 6)
+ (pat-array uint32 :offset 8)
+ (bucket-array uint32 :offset 12)
+ (grid-step vector :inline)
+ (bbox bounding-box :inline)
+ (bbox4w bounding-box4w :inline)
+ (axis-scale vector :inline :overlay-at (-> bbox max))
+ (avg-extents vector :inline :overlay-at (-> bbox4w min data 0))
+ (dimension-array uint32 4 :overlay-at (-> grid-step data 3))
+ (stats collide-hash-fragment-stats :inline :overlay-at (-> bbox min data 3))
+ (num-verts uint16 :overlay-at (-> bbox min data 3))
+ (num-polys uint8 :offset 62)
+ (poly-count uint8 :offset 63)
+ (poly-array uint32 :overlay-at (-> bbox max data 3))
+ (vert-array uint32 :overlay-at (-> bbox4w min data 3))
+ (index-array uint32 :overlay-at (-> bbox4w max data 3))
+ )
+ )
+
+;; definition for method 3 of type collide-hash-fragment
+(defmethod inspect ((this collide-hash-fragment))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this (-> this type))
+ (format #t "~1Tid: ~D~%" (-> this id))
+ (format #t "~1Tbsphere: ~`vector`P~%" (-> this bsphere))
+ (format #t "~1Tnum-buckets: ~D~%" (-> this num-buckets))
+ (format #t "~1Tnum-indices: ~D~%" (-> this num-indices))
+ (format #t "~1Tpat-array: #x~X~%" (-> this pat-array))
+ (format #t "~1Tbucket-array: #x~X~%" (-> this bucket-array))
+ (format #t "~1Tgrid-step: #~%" (-> this grid-step))
+ (format #t "~1Tbbox: #~%" (-> this bbox))
+ (format #t "~1Tbbox4w: #~%" (-> this bbox4w))
+ (format #t "~1Taxis-scale: #~%" (-> this bbox max))
+ (format #t "~1Tavg-extents: #~%" (-> this bbox4w))
+ (format #t "~1Tdimension-array[4] @ #x~X~%" (&-> this grid-step w))
+ (format #t "~1Tstats: #~%" (&-> this bbox min w))
+ (format #t "~1Tnum-verts: ~D~%" (-> this stats num-verts))
+ (format #t "~1Tnum-polys: ~D~%" (-> this stats num-polys))
+ (format #t "~1Tpoly-count: ~D~%" (-> this stats poly-count))
+ (format #t "~1Tpoly-array: #x~X~%" (-> this bbox max w))
+ (format #t "~1Tvert-array: #x~X~%" (-> this avg-extents w))
+ (format #t "~1Tindex-array: #x~X~%" (-> this index-array))
+ (label cfg-4)
+ this
+ )
+
+;; definition of type collide-hash-fragment-array
+(deftype collide-hash-fragment-array (array)
+ "A collection of collide-hash-fragments. These are used by the instanced collision if a single instance
+needs more than 1 collide-hash-fragment worth of triangles."
+ ((fragments collide-hash-fragment :dynamic :offset 16)
+ )
+ )
+
+;; definition for method 3 of type collide-hash-fragment-array
+(defmethod inspect ((this collide-hash-fragment-array))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this (-> this type))
+ (format #t "~1Ttype: ~A~%" (-> this type))
+ (format #t "~1Tlength: ~D~%" (-> this length))
+ (format #t "~1Tallocated-length: ~D~%" (-> this allocated-length))
+ (format #t "~1Tcontent-type: ~A~%" (-> this content-type))
+ (label cfg-4)
+ this
+ )
+
+;; definition of type collide-hash
+(deftype collide-hash (drawable)
+ ((num-ids uint16 :overlay-at id)
+ (id-count uint16 :offset 6)
+ (num-buckets uint32 :offset 8)
+ (qwc-id-bits uint32 :offset 12)
+ (grid-step vector :inline :overlay-at bsphere)
+ (bbox bounding-box :inline :offset 32)
+ (bbox4w bounding-box4w :inline :offset 64)
+ (axis-scale vector :inline :offset 48)
+ (avg-extents vector :inline :offset 64)
+ (bucket-array uint32 :offset 44)
+ (item-array (inline-array collide-hash-item) :overlay-at (-> axis-scale w))
+ (dimension-array uint32 3 :overlay-at (-> avg-extents w))
+ (num-items uint32 :offset 92)
+ )
+ )
+
+;; definition for method 3 of type collide-hash
+(defmethod inspect ((this collide-hash))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this (-> this type))
+ (format #t "~1Tid: ~D~%" (-> this id))
+ (format #t "~1Tbsphere: ~`vector`P~%" (-> this bsphere))
+ (format #t "~1Tnum-ids: ~D~%" (-> this num-ids))
+ (format #t "~1Tid-count: ~D~%" (-> this id-count))
+ (format #t "~1Tnum-buckets: ~D~%" (-> this num-buckets))
+ (format #t "~1Tqwc-id-bits: ~D~%" (-> this qwc-id-bits))
+ (format #t "~1Tgrid-step: #~%" (-> this bsphere))
+ (format #t "~1Tbbox: #~%" (-> this bbox))
+ (format #t "~1Tbbox4w: #~%" (-> this bbox4w))
+ (format #t "~1Taxis-scale: #~%" (-> this bbox max))
+ (format #t "~1Tavg-extents: #~%" (-> this bbox4w))
+ (format #t "~1Tbucket-array: #x~X~%" (-> this bbox min w))
+ (format #t "~1Titem-array: #x~X~%" (-> this bbox max w))
+ (format #t "~1Tdimension-array[3] @ #x~X~%" (&-> this bbox4w min w))
+ (format #t "~1Tnum-items: ~D~%" (-> this num-items))
+ (label cfg-4)
+ this
+ )
+
+;; failed to figure out what this is:
+0
+
+
+
+
diff --git a/test/decompiler/reference/jak3/engine/gfx/hfrag/hfrag-h_REF.gc b/test/decompiler/reference/jak3/engine/gfx/hfrag/hfrag-h_REF.gc
new file mode 100644
index 0000000000..b6dbea68cb
--- /dev/null
+++ b/test/decompiler/reference/jak3/engine/gfx/hfrag/hfrag-h_REF.gc
@@ -0,0 +1,1071 @@
+;;-*-Lisp-*-
+(in-package goal)
+
+;; definition of type adgif-shader-array
+(deftype adgif-shader-array (inline-array-class)
+ ((data adgif-shader :inline :dynamic)
+ )
+ )
+
+;; definition for method 3 of type adgif-shader-array
+(defmethod inspect ((this adgif-shader-array))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this (-> this type))
+ (format #t "~1Tlength: ~D~%" (-> this length))
+ (format #t "~1Tallocated-length: ~D~%" (-> this allocated-length))
+ (format #t "~1Tdata[0] @ #x~X~%" (-> this data))
+ (label cfg-4)
+ this
+ )
+
+;; failed to figure out what this is:
+(set! (-> adgif-shader-array heap-base) (the-as uint 80))
+
+;; definition of type hfrag-montage
+(deftype hfrag-montage (structure)
+ ((data uint16 16)
+ )
+ )
+
+;; definition for method 3 of type hfrag-montage
+(defmethod inspect ((this hfrag-montage))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'hfrag-montage)
+ (format #t "~1Tdata[16] @ #x~X~%" (-> this data))
+ (label cfg-4)
+ this
+ )
+
+;; definition of type hfrag-bucket
+(deftype hfrag-bucket (structure)
+ ((next uint32)
+ (count uint16)
+ (vertex-count uint16)
+ (next-scissor uint32)
+ (count-scissor uint16)
+ (vertex-count-scissor uint16)
+ )
+ )
+
+;; definition for method 3 of type hfrag-bucket
+(defmethod inspect ((this hfrag-bucket))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'hfrag-bucket)
+ (format #t "~1Tnext: #x~X~%" (-> this next))
+ (format #t "~1Tcount: ~D~%" (-> this count))
+ (format #t "~1Tvertex-count: ~D~%" (-> this vertex-count))
+ (format #t "~1Tnext-scissor: #x~X~%" (-> this next-scissor))
+ (format #t "~1Tcount-scissor: ~D~%" (-> this count-scissor))
+ (format #t "~1Tvertex-count-scissor: ~D~%" (-> this vertex-count-scissor))
+ (label cfg-4)
+ this
+ )
+
+;; definition of type hfrag-packed-index
+(deftype hfrag-packed-index (uint16)
+ ()
+ )
+
+;; definition of type hfrag-vertex
+(deftype hfrag-vertex (structure)
+ ((height uint16)
+ (packed-index hfrag-packed-index)
+ )
+ )
+
+;; definition for method 3 of type hfrag-vertex
+(defmethod inspect ((this hfrag-vertex))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'hfrag-vertex)
+ (format #t "~1Theight: ~D~%" (-> this height))
+ (format #t "~1Tpacked-index: ~D~%" (-> this packed-index))
+ (label cfg-4)
+ this
+ )
+
+;; definition of type hfrag-vert-index
+(deftype hfrag-vert-index (structure)
+ ((pos vector2ub :inline)
+ (index0 uint16 :offset 2)
+ (index1 uint16 :offset 4)
+ (index2 uint16 :offset 6)
+ )
+ :pack-me
+ )
+
+;; definition for method 3 of type hfrag-vert-index
+(defmethod inspect ((this hfrag-vert-index))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'hfrag-vert-index)
+ (format #t "~1Tpos: #~%" (-> this pos))
+ (format #t "~1Tindex0: ~D~%" (-> this index0))
+ (format #t "~1Tindex1: ~D~%" (-> this index1))
+ (format #t "~1Tindex2: ~D~%" (-> this index2))
+ (label cfg-4)
+ this
+ )
+
+;; definition of type hfrag-poly4
+(deftype hfrag-poly4 (structure)
+ ((data hfrag-vert-index 4 :inline)
+ )
+ )
+
+;; definition for method 3 of type hfrag-poly4
+;; INFO: this function exists in multiple non-identical object files
+(defmethod inspect ((this hfrag-poly4))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'hfrag-poly4)
+ (format #t "~1Tdata[4] @ #x~X~%" (-> this data))
+ (label cfg-4)
+ this
+ )
+
+;; definition for method 3 of type hfrag-poly4
+;; INFO: this function exists in multiple non-identical object files
+(defmethod inspect ((this hfrag-poly4))
+ (format #t "[~8x] hfrag-poly4~%" this)
+ (dotimes (s5-0 4)
+ (format
+ #t
+ "~T~d: index0: ~d index1: ~d index2: ~d pos: ~d ~d~%"
+ s5-0
+ (shr (-> this data s5-0 index0) 2)
+ (shr (-> this data s5-0 index1) 2)
+ (shr (-> this data s5-0 index2) 2)
+ (-> this data s5-0 pos x)
+ (-> this data s5-0 pos y)
+ )
+ )
+ this
+ )
+
+;; definition of type hfrag-poly9
+(deftype hfrag-poly9 (structure)
+ ((data hfrag-vert-index 9 :inline)
+ )
+ )
+
+;; definition for method 3 of type hfrag-poly9
+;; INFO: this function exists in multiple non-identical object files
+(defmethod inspect ((this hfrag-poly9))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'hfrag-poly9)
+ (format #t "~1Tdata[9] @ #x~X~%" (-> this data))
+ (label cfg-4)
+ this
+ )
+
+;; definition for method 3 of type hfrag-poly9
+;; INFO: this function exists in multiple non-identical object files
+(defmethod inspect ((this hfrag-poly9))
+ (format #t "[~8x] hfrag-poly9~%" this)
+ (dotimes (s5-0 9)
+ (format
+ #t
+ "~T~d: index0: ~d index1: ~d index2: ~d pos: ~d ~d~%"
+ s5-0
+ (shr (-> this data s5-0 index0) 2)
+ (shr (-> this data s5-0 index1) 2)
+ (shr (-> this data s5-0 index2) 2)
+ (-> this data s5-0 pos x)
+ (-> this data s5-0 pos y)
+ )
+ )
+ this
+ )
+
+;; definition of type hfrag-poly25
+(deftype hfrag-poly25 (structure)
+ ((data hfrag-vert-index 25 :inline)
+ )
+ )
+
+;; definition for method 3 of type hfrag-poly25
+;; INFO: this function exists in multiple non-identical object files
+(defmethod inspect ((this hfrag-poly25))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'hfrag-poly25)
+ (format #t "~1Tdata[25] @ #x~X~%" (-> this data))
+ (label cfg-4)
+ this
+ )
+
+;; definition for method 3 of type hfrag-poly25
+;; INFO: this function exists in multiple non-identical object files
+(defmethod inspect ((this hfrag-poly25))
+ (format #t "[~8x] hfrag-poly9~%" this)
+ (dotimes (s5-0 25)
+ (format
+ #t
+ "~T~d: index0: ~d index1: ~d index2: ~d pos: ~d ~d~%"
+ s5-0
+ (shr (-> this data s5-0 index0) 2)
+ (shr (-> this data s5-0 index1) 2)
+ (shr (-> this data s5-0 index2) 2)
+ (-> this data s5-0 pos x)
+ (-> this data s5-0 pos y)
+ )
+ )
+ this
+ )
+
+;; definition of type hfrag-poly4-chain
+(deftype hfrag-poly4-chain (structure)
+ ((tag dma-packet :inline)
+ (verts vector4w-3 4 :inline)
+ (next dma-packet :inline :offset 208)
+ )
+ )
+
+;; definition for method 3 of type hfrag-poly4-chain
+(defmethod inspect ((this hfrag-poly4-chain))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'hfrag-poly4-chain)
+ (format #t "~1Ttag: #~%" (-> this tag))
+ (format #t "~1Tverts[4] @ #x~X~%" (-> this verts))
+ (format #t "~1Tnext: #~%" (-> this next))
+ (label cfg-4)
+ this
+ )
+
+;; definition of type hfrag-poly9-chain
+(deftype hfrag-poly9-chain (structure)
+ ((tag dma-packet :inline)
+ (verts vector4w-3 12 :inline)
+ (next dma-packet :inline :offset 592)
+ )
+ )
+
+;; definition for method 3 of type hfrag-poly9-chain
+(defmethod inspect ((this hfrag-poly9-chain))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'hfrag-poly9-chain)
+ (format #t "~1Ttag: #~%" (-> this tag))
+ (format #t "~1Tverts[12] @ #x~X~%" (-> this verts))
+ (format #t "~1Tnext: #~%" (-> this next))
+ (label cfg-4)
+ this
+ )
+
+;; definition of type hfrag-poly25-chain
+(deftype hfrag-poly25-chain (structure)
+ ((tag dma-packet :inline)
+ (verts vector4w-3 40 :inline)
+ (next dma-packet :inline)
+ )
+ )
+
+;; definition for method 3 of type hfrag-poly25-chain
+(defmethod inspect ((this hfrag-poly25-chain))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'hfrag-poly25-chain)
+ (format #t "~1Ttag: #~%" (-> this tag))
+ (format #t "~1Tverts[40] @ #x~X~%" (-> this verts))
+ (format #t "~1Tnext: #~%" (-> this next))
+ (label cfg-4)
+ this
+ )
+
+;; definition of type hfrag-cache-vertex
+(deftype hfrag-cache-vertex (structure)
+ ((color vector4w :inline)
+ (pos vector :inline)
+ (clip uint32 :overlay-at (-> pos data 3))
+ )
+ )
+
+;; definition for method 3 of type hfrag-cache-vertex
+(defmethod inspect ((this hfrag-cache-vertex))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'hfrag-cache-vertex)
+ (format #t "~1Tcolor: #~%" (-> this color))
+ (format #t "~1Tpos: #~%" (-> this pos))
+ (format #t "~1Tclip: ~D~%" (-> this pos w))
+ (label cfg-4)
+ this
+ )
+
+;; definition of type hfrag-cache-line
+(deftype hfrag-cache-line (structure)
+ ((data hfrag-cache-vertex 9 :inline)
+ )
+ )
+
+;; definition for method 3 of type hfrag-cache-line
+(defmethod inspect ((this hfrag-cache-line))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'hfrag-cache-line)
+ (format #t "~1Tdata[9] @ #x~X~%" (-> this data))
+ (label cfg-4)
+ this
+ )
+
+;; definition of type hfrag-visbits
+(deftype hfrag-visbits (structure)
+ ((data uint8 128)
+ )
+ )
+
+;; definition for method 3 of type hfrag-visbits
+(defmethod inspect ((this hfrag-visbits))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'hfrag-visbits)
+ (format #t "~1Tdata[128] @ #x~X~%" (-> this data))
+ (label cfg-4)
+ this
+ )
+
+;; definition of type hfrag-gcf-control
+(deftype hfrag-gcf-control (structure)
+ ((matrix matrix :inline)
+ (giftag generic-gif-tag :inline)
+ (adnops gs-adcmd 2 :inline)
+ (num-strips uint32 :overlay-at (-> giftag data 3))
+ (num-dps uint32 :overlay-at (-> adnops 0 word 3))
+ (kick-offset uint32 :offset 108)
+ (shader gcf-shader :inline)
+ )
+ )
+
+;; definition for method 3 of type hfrag-gcf-control
+(defmethod inspect ((this hfrag-gcf-control))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'hfrag-gcf-control)
+ (format #t "~1Tmatrix: #~%" (-> this matrix))
+ (format #t "~1Tgiftag: #~%" (-> this giftag))
+ (format #t "~1Tadnops[2] @ #x~X~%" (-> this adnops))
+ (format #t "~1Tnum-strips: ~D~%" (-> this giftag num-strips))
+ (format #t "~1Tnum-dps: ~D~%" (-> this num-dps))
+ (format #t "~1Tkick-offset: ~D~%" (-> this kick-offset))
+ (format #t "~1Tshader: #~%" (-> this shader))
+ (label cfg-4)
+ this
+ )
+
+;; definition of type hfrag-gcf-ctrl
+(deftype hfrag-gcf-ctrl (structure)
+ ((tag dma-packet :inline)
+ (control hfrag-gcf-control :inline)
+ )
+ )
+
+;; definition for method 3 of type hfrag-gcf-ctrl
+(defmethod inspect ((this hfrag-gcf-ctrl))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'hfrag-gcf-ctrl)
+ (format #t "~1Ttag: #~%" (-> this tag))
+ (format #t "~1Tcontrol: #~%" (-> this control))
+ (label cfg-4)
+ this
+ )
+
+;; definition of type hfrag-init-packet
+(deftype hfrag-init-packet (structure)
+ ((init-tmpl dma-packet :inline)
+ (init-data uint32 8)
+ )
+ )
+
+;; definition for method 3 of type hfrag-init-packet
+(defmethod inspect ((this hfrag-init-packet))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'hfrag-init-packet)
+ (format #t "~1Tinit-tmpl: #~%" (-> this init-tmpl))
+ (format #t "~1Tinit-data[8] @ #x~X~%" (-> this init-data))
+ (format #t "~1Tquad[3] @ #x~X~%" (-> this init-tmpl))
+ (label cfg-4)
+ this
+ )
+
+;; definition of type hfrag-sprite-coord
+(deftype hfrag-sprite-coord (structure)
+ ((pos0 vector4w :inline)
+ (pos1 vector4w :inline)
+ )
+ )
+
+;; definition for method 3 of type hfrag-sprite-coord
+(defmethod inspect ((this hfrag-sprite-coord))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'hfrag-sprite-coord)
+ (format #t "~1Tpos0: #~%" (-> this pos0))
+ (format #t "~1Tpos1: #~%" (-> this pos1))
+ (label cfg-4)
+ this
+ )
+
+;; definition of type hfrag-montage-coord
+(deftype hfrag-montage-coord (structure)
+ ((stq0 vector4 :inline)
+ (stq1 vector4 :inline)
+ )
+ )
+
+;; definition for method 3 of type hfrag-montage-coord
+(defmethod inspect ((this hfrag-montage-coord))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'hfrag-montage-coord)
+ (format #t "~1Tstq0: #~%" (-> this stq0))
+ (format #t "~1Tstq1: #~%" (-> this stq1))
+ (label cfg-4)
+ this
+ )
+
+;; definition of type hfrag-sprite-packet
+(deftype hfrag-sprite-packet (structure)
+ ((sprite-tmpl dma-gif-packet :inline)
+ (color vector4w :inline)
+ (tex0 vector :inline)
+ (pos0 vector4w :inline)
+ (tex1 vector :inline)
+ (pos1 vector4w :inline)
+ )
+ )
+
+;; definition for method 3 of type hfrag-sprite-packet
+(defmethod inspect ((this hfrag-sprite-packet))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'hfrag-sprite-packet)
+ (format #t "~1Tsprite-tmpl: #~%" (-> this sprite-tmpl))
+ (format #t "~1Tcolor: #~%" (-> this color))
+ (format #t "~1Ttex0: #~%" (-> this tex0))
+ (format #t "~1Tpos0: #~%" (-> this pos0))
+ (format #t "~1Ttex1: #~%" (-> this tex1))
+ (format #t "~1Tpos1: #~%" (-> this pos1))
+ (label cfg-4)
+ this
+ )
+
+;; definition of type hfrag-tex-data
+(deftype hfrag-tex-data (structure)
+ ((quad qword 3 :inline)
+ (prims uint64 6 :overlay-at quad)
+ (reg-0 uint8 :overlay-at (-> quad 0 data 2))
+ (reg-1 uint8 :overlay-at (-> prims 3))
+ (reg-2 uint8 :overlay-at (-> prims 5))
+ (tex0 uint64 :overlay-at (-> quad 0 data 0))
+ (tex1 uint64 :overlay-at (-> prims 2))
+ (texflush uint64 :overlay-at (-> prims 4))
+ )
+ )
+
+;; definition for method 3 of type hfrag-tex-data
+(defmethod inspect ((this hfrag-tex-data))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'hfrag-tex-data)
+ (format #t "~1Tquad[3] @ #x~X~%" (-> this quad))
+ (format #t "~1Tprims[6] @ #x~X~%" (-> this quad))
+ (format #t "~1Treg-0: ~D~%" (-> this reg-0))
+ (format #t "~1Treg-1: ~D~%" (-> this reg-1))
+ (format #t "~1Treg-2: ~D~%" (-> this reg-2))
+ (format #t "~1Ttex0: #x~X~%" (-> this tex0))
+ (format #t "~1Ttex1: #x~X~%" (-> this tex1))
+ (format #t "~1Ttexflush: #x~X~%" (-> this texflush))
+ (label cfg-4)
+ this
+ )
+
+;; definition of type hfrag-mip-packet
+(deftype hfrag-mip-packet (structure)
+ ((mip-tmpl dma-gif-packet :inline)
+ (tex0-1 vector :inline)
+ (tex1-1 vector :inline)
+ (texflush vector :inline)
+ (color vector4w :inline)
+ (tex0 vector :inline)
+ (pos0 vector :inline)
+ (tex1 vector :inline)
+ (pos1 vector :inline)
+ )
+ )
+
+;; definition for method 3 of type hfrag-mip-packet
+;; INFO: this function exists in multiple non-identical object files
+(defmethod inspect ((this hfrag-mip-packet))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'hfrag-mip-packet)
+ (format #t "~1Tsprite-tmpl: #~%" (-> this mip-tmpl))
+ (format #t "~1Tadnop0: #~%" (-> this tex0-1))
+ (format #t "~1Tadnop1: #~%" (-> this tex1-1))
+ (format #t "~1Tcolor: #~%" (-> this texflush))
+ (format #t "~1Ttex0: #~%" (-> this color))
+ (format #t "~1Tpos0: #~%" (-> this tex0))
+ (format #t "~1Ttex1: #~%" (-> this pos0))
+ (format #t "~1Tpos1: #~%" (-> this tex1))
+ (label cfg-4)
+ this
+ )
+
+;; definition of type hfrag-adgif-packet
+(deftype hfrag-adgif-packet (structure)
+ ((adgif-tmpl dma-gif-packet :inline)
+ (adgif-data adgif-shader :inline)
+ )
+ )
+
+;; definition for method 3 of type hfrag-adgif-packet
+(defmethod inspect ((this hfrag-adgif-packet))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'hfrag-adgif-packet)
+ (format #t "~1Tadgif-tmpl: #~%" (-> this adgif-tmpl))
+ (format #t "~1Tadgif-data: #~%" (-> this adgif-data))
+ (label cfg-4)
+ this
+ )
+
+;; definition of type hfrag-adgif-packet2
+(deftype hfrag-adgif-packet2 (structure)
+ ((adgif-tmpl dma-gif-packet :inline)
+ (adgif-data adgif-shader :inline)
+ (texflush uint128)
+ )
+ )
+
+;; definition for method 3 of type hfrag-adgif-packet2
+;; INFO: Used lq/sq
+(defmethod inspect ((this hfrag-adgif-packet2))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'hfrag-adgif-packet2)
+ (format #t "~1Tadgif-tmpl: #~%" (-> this adgif-tmpl))
+ (format #t "~1Tadgif-data: #~%" (-> this adgif-data))
+ (format #t "~1Ttexflush: ~D~%" (-> this texflush))
+ (label cfg-4)
+ this
+ )
+
+;; definition of type hfrag-frame
+(deftype hfrag-frame (structure)
+ ((quad qword 4 :inline :offset 0)
+ (prims uint64 8 :overlay-at quad)
+ (reg-0 uint8 :overlay-at (-> prims 1))
+ (reg-1 uint8 :overlay-at (-> prims 3))
+ (reg-2 uint8 :overlay-at (-> prims 5))
+ (reg-3 uint8 :overlay-at (-> prims 7))
+ (frame uint64 :overlay-at (-> prims 0))
+ (scissor uint64 :overlay-at (-> prims 2))
+ (xyoffset uint64 :overlay-at (-> prims 4))
+ (test uint64 :overlay-at (-> prims 6))
+ )
+ )
+
+;; definition for method 3 of type hfrag-frame
+(defmethod inspect ((this hfrag-frame))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'hfrag-frame)
+ (format #t "~1Tquad[4] @ #x~X~%" (-> this quad))
+ (format #t "~1Tprims[8] @ #x~X~%" (-> this quad))
+ (format #t "~1Treg-0: ~D~%" (-> this reg-0))
+ (format #t "~1Treg-1: ~D~%" (-> this reg-1))
+ (format #t "~1Treg-2: ~D~%" (-> this reg-2))
+ (format #t "~1Treg-3: ~D~%" (-> this reg-3))
+ (format #t "~1Tframe: #x~X~%" (-> this frame))
+ (format #t "~1Tscissor: #x~X~%" (-> this scissor))
+ (format #t "~1Txyoffset: #x~X~%" (-> this xyoffset))
+ (format #t "~1Ttest: #x~X~%" (-> this test))
+ (label cfg-4)
+ this
+ )
+
+;; definition of type hfrag-frame-packet
+(deftype hfrag-frame-packet (structure)
+ ((frame-tmpl dma-gif-packet :inline)
+ (frame-data hfrag-frame :inline)
+ )
+ )
+
+;; definition for method 3 of type hfrag-frame-packet
+(defmethod inspect ((this hfrag-frame-packet))
+ (when (not this)
+ (set! this this)
+ (goto cfg-4)
+ )
+ (format #t "[~8x] ~A~%" this 'hfrag-frame-packet)
+ (format #t "~1Tframe-tmpl: #