mirror of
https://github.com/open-goal/jak-project
synced 2026-05-27 16:14:18 -04:00
710f3ac117
Custom levels for Jak 2/3 now support envmapped TIE geometry. The TIE extract was also changed to ignore materials that have the specular flag set, but are missing a roughness texture. Jak 2/3 now also support the `build-actor` tool. The `build-custom-level` and `build-actor` macros now have a few new options: - Both now have a `force-run` option (`#f` by default) that, when set to `#t`, will always run level/art group generation even if the output files are up to date. - `build-custom-level` has a `gen-fr3` option (`#t` by default) that, when set to `#f`, will skip generating the FR3 file for the custom level and only generate the GOAL level file to skip the potentially slow process of finding and adding art groups and textures. Useful for when you want to temporarily edit only the GOAL side of the level (such as entity placement, etc.). - `build-actor` has a `texture-bucket` option (default 0) which will determine what DMA sink group the model will be placed in, which is useful to determine the draw order of the model. Previously, this was omitted, resulting in shadows not drawing over custom actors because the actors were put in a bucket that is drawn after shadows (this behavior can be restored with `:texture-bucket #f`).
401 lines
15 KiB
C++
401 lines
15 KiB
C++
#include "Tools.h"
|
|
|
|
#include "common/goos/ParseHelpers.h"
|
|
#include "common/util/DgoWriter.h"
|
|
#include "common/util/FileUtil.h"
|
|
|
|
#include "goalc/build_actor/jak1/build_actor.h"
|
|
#include "goalc/build_level/jak1/build_level.h"
|
|
#include "goalc/build_level/jak2/build_level.h"
|
|
#include "goalc/build_level/jak3/build_level.h"
|
|
#include "goalc/compiler/Compiler.h"
|
|
#include "goalc/data_compiler/dir_tpages.h"
|
|
#include "goalc/data_compiler/game_count.h"
|
|
#include "goalc/data_compiler/game_text_common.h"
|
|
|
|
#include "fmt/core.h"
|
|
|
|
CompilerTool::CompilerTool(Compiler* compiler) : Tool("goalc"), m_compiler(compiler) {}
|
|
|
|
bool CompilerTool::needs_run(const ToolInput& task, const PathMap& path_map) {
|
|
if (task.input.size() != 1) {
|
|
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
|
}
|
|
|
|
if (!m_compiler->knows_object_file(fs::path(task.input.at(0)).stem().string())) {
|
|
return true;
|
|
}
|
|
return Tool::needs_run(task, path_map);
|
|
}
|
|
|
|
bool CompilerTool::run(const ToolInput& task, const PathMap& /*path_map*/) {
|
|
// todo check inputs
|
|
try {
|
|
CompilationOptions options;
|
|
options.filename = task.input.at(0);
|
|
options.color = true;
|
|
options.write = true;
|
|
m_compiler->asm_file(options);
|
|
} catch (std::exception& e) {
|
|
lg::print("Compilation failed: {}\n", e.what());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
namespace {
|
|
DgoDescription parse_desc_file(const std::string& filename, goos::Reader& reader) {
|
|
auto& dgo_desc = reader.read_from_file({filename}).as_pair()->cdr;
|
|
if (goos::list_length(dgo_desc) != 1) {
|
|
throw std::runtime_error("Invalid DGO description - got too many lists");
|
|
}
|
|
auto& dgo = dgo_desc.as_pair()->car;
|
|
|
|
DgoDescription desc;
|
|
auto& first = dgo.as_pair()->car;
|
|
desc.dgo_name = first.as_string()->data;
|
|
auto& dgo_rest = dgo.as_pair()->cdr.as_pair()->car;
|
|
|
|
for_each_in_list(dgo_rest, [&](const goos::Object& entry) {
|
|
if (!entry.is_string()) {
|
|
throw std::runtime_error(fmt::format("Invalid file name for DGO: {}\n", entry.print()));
|
|
}
|
|
|
|
DgoDescription::DgoEntry o;
|
|
const auto& file_name = entry.as_string()->data;
|
|
// automatically deduce dgo name
|
|
// (not really a fan of how this is written...)
|
|
if (file_name.length() > 2 && file_name.substr(file_name.length() - 2, 2) == ".o") {
|
|
// ends with .o so it's a code file
|
|
o.name_in_dgo = file_name.substr(0, file_name.length() - 2);
|
|
} else if (file_name.length() > 6 && file_name.substr(file_name.length() - 6, 6) == "-ag.go") {
|
|
// ends with -ag.go so it's an art group file
|
|
o.name_in_dgo = file_name.substr(0, file_name.length() - 6);
|
|
} else if (file_name.length() > 3 && file_name.substr(file_name.length() - 3, 3) == ".go") {
|
|
// ends with .go so it's a generic data file
|
|
o.name_in_dgo = file_name.substr(0, file_name.length() - 3);
|
|
}
|
|
o.file_name = file_name;
|
|
desc.entries.push_back(o);
|
|
});
|
|
return desc;
|
|
}
|
|
} // namespace
|
|
|
|
DgoTool::DgoTool() : Tool("dgo") {}
|
|
|
|
bool DgoTool::run(const ToolInput& task, const PathMap& path_map) {
|
|
if (task.input.size() != 1) {
|
|
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
|
}
|
|
auto desc = parse_desc_file(task.input.at(0), m_reader);
|
|
build_dgo(desc, path_map.output_prefix);
|
|
return true;
|
|
}
|
|
|
|
std::vector<std::string> DgoTool::get_additional_dependencies(const ToolInput& task,
|
|
const PathMap& path_map) {
|
|
std::vector<std::string> result;
|
|
auto desc = parse_desc_file(task.input.at(0), m_reader);
|
|
for (auto& x : desc.entries) {
|
|
// todo out
|
|
result.push_back(fmt::format("out/{}obj/{}", path_map.output_prefix, x.file_name));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
TpageDirTool::TpageDirTool() : Tool("tpage-dir") {}
|
|
|
|
bool TpageDirTool::run(const ToolInput& task, const PathMap& path_map) {
|
|
if (task.input.size() != 1) {
|
|
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
|
}
|
|
compile_dir_tpages(task.input.at(0), path_map.output_prefix);
|
|
return true;
|
|
}
|
|
|
|
CopyTool::CopyTool() : Tool("copy") {}
|
|
|
|
bool CopyTool::run(const ToolInput& task, const PathMap& /*path_map*/) {
|
|
if (task.input.size() != 1) {
|
|
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
|
}
|
|
for (auto& out : task.output) {
|
|
fs::copy(fs::path(file_util::get_file_path({task.input.at(0)})),
|
|
fs::path(file_util::get_file_path({out})), fs::copy_options::overwrite_existing);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
GameCntTool::GameCntTool() : Tool("game-cnt") {}
|
|
|
|
bool GameCntTool::run(const ToolInput& task, const PathMap& path_map) {
|
|
if (task.input.size() != 1) {
|
|
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
|
}
|
|
compile_game_count(task.input.at(0), path_map.output_prefix);
|
|
return true;
|
|
}
|
|
|
|
TextTool::TextTool() : Tool("text") {}
|
|
|
|
bool TextTool::needs_run(const ToolInput& task, const PathMap& path_map) {
|
|
if (task.input.size() != 1) {
|
|
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
|
}
|
|
|
|
std::vector<std::string> deps;
|
|
std::vector<GameTextDefinitionFile> files;
|
|
open_text_project("text", task.input.at(0), files);
|
|
for (auto& file : files) {
|
|
deps.push_back(path_map.apply_remaps(file.file_path));
|
|
}
|
|
return Tool::needs_run({task.input, deps, task.output, task.arg}, path_map);
|
|
}
|
|
|
|
bool TextTool::run(const ToolInput& task, const PathMap& path_map) {
|
|
GameTextDB db;
|
|
std::vector<GameTextDefinitionFile> files;
|
|
open_text_project("text", task.input.at(0), files);
|
|
for (auto& file : files) {
|
|
file.file_path = path_map.apply_remaps(file.file_path);
|
|
}
|
|
compile_game_text(files, db, path_map.output_prefix);
|
|
return true;
|
|
}
|
|
|
|
GroupTool::GroupTool() : Tool("group") {}
|
|
|
|
bool GroupTool::run(const ToolInput&, const PathMap& /*path_map*/) {
|
|
return true;
|
|
}
|
|
|
|
void enumerate_subtitle_project_files(const std::string& tool_name,
|
|
const std::string& file_path,
|
|
const PathMap& path_map,
|
|
std::vector<GameSubtitleDefinitionFile>& files,
|
|
std::vector<std::string>& deps) {
|
|
open_subtitle_project(tool_name, file_path, files);
|
|
for (auto& file : files) {
|
|
deps.push_back(path_map.apply_remaps(file.lines_path));
|
|
deps.push_back(path_map.apply_remaps(file.meta_path));
|
|
if (file.lines_base_path) {
|
|
deps.push_back(path_map.apply_remaps(file.lines_base_path.value()));
|
|
}
|
|
if (file.meta_base_path) {
|
|
deps.push_back(path_map.apply_remaps(file.meta_base_path.value()));
|
|
}
|
|
}
|
|
}
|
|
|
|
void run_subtitle_project_files(const std::string& tool_name,
|
|
const std::string& file_path,
|
|
const PathMap& path_map,
|
|
std::vector<GameSubtitleDefinitionFile>& files) {
|
|
open_subtitle_project(tool_name, file_path, files);
|
|
for (auto& file : files) {
|
|
file.lines_path = path_map.apply_remaps(file.lines_path);
|
|
file.meta_path = path_map.apply_remaps(file.meta_path);
|
|
if (file.lines_base_path) {
|
|
file.lines_base_path = path_map.apply_remaps(file.lines_base_path.value());
|
|
}
|
|
if (file.meta_base_path) {
|
|
file.meta_base_path = path_map.apply_remaps(file.meta_base_path.value());
|
|
}
|
|
}
|
|
}
|
|
|
|
SubtitleTool::SubtitleTool() : Tool("subtitle") {}
|
|
|
|
bool SubtitleTool::needs_run(const ToolInput& task, const PathMap& path_map) {
|
|
if (task.input.size() != 1) {
|
|
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
|
}
|
|
std::vector<GameSubtitleDefinitionFile> files;
|
|
std::vector<std::string> deps;
|
|
enumerate_subtitle_project_files(name(), task.input.at(0), path_map, files, deps);
|
|
return Tool::needs_run({task.input, deps, task.output, task.arg}, path_map);
|
|
}
|
|
|
|
bool SubtitleTool::run(const ToolInput& task, const PathMap& path_map) {
|
|
GameSubtitleDB db;
|
|
db.m_subtitle_version = GameSubtitleDB::SubtitleFormat::V1;
|
|
std::vector<GameSubtitleDefinitionFile> files;
|
|
run_subtitle_project_files(name(), task.input.at(0), path_map, files);
|
|
compile_game_subtitles(files, db, path_map.output_prefix);
|
|
return true;
|
|
}
|
|
|
|
SubtitleV2Tool::SubtitleV2Tool() : Tool("subtitle-v2") {}
|
|
|
|
bool SubtitleV2Tool::needs_run(const ToolInput& task, const PathMap& path_map) {
|
|
if (task.input.size() != 1) {
|
|
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
|
}
|
|
std::vector<GameSubtitleDefinitionFile> files;
|
|
std::vector<std::string> deps;
|
|
enumerate_subtitle_project_files(name(), task.input.at(0), path_map, files, deps);
|
|
return Tool::needs_run({task.input, deps, task.output, task.arg}, path_map);
|
|
}
|
|
|
|
bool SubtitleV2Tool::run(const ToolInput& task, const PathMap& path_map) {
|
|
GameSubtitleDB db;
|
|
db.m_subtitle_version = GameSubtitleDB::SubtitleFormat::V2;
|
|
std::vector<GameSubtitleDefinitionFile> files;
|
|
run_subtitle_project_files(name(), task.input.at(0), path_map, files);
|
|
compile_game_subtitles(files, db, path_map.output_prefix);
|
|
return true;
|
|
}
|
|
|
|
BuildLevelTool::BuildLevelTool() : Tool("build-level") {}
|
|
|
|
bool BuildLevelTool::needs_run(const ToolInput& task, const PathMap& path_map) {
|
|
if (task.input.size() > 3) {
|
|
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
|
}
|
|
auto deps = get_build_level_deps(task.input.at(0));
|
|
auto rerun = task.input.at(1) == "#t";
|
|
std::vector in = {task.input.at(0)};
|
|
return rerun || Tool::needs_run({in, deps, task.output, task.arg}, path_map);
|
|
}
|
|
|
|
bool BuildLevelTool::run(const ToolInput& task, const PathMap& path_map) {
|
|
if (task.input.size() > 3) {
|
|
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
|
}
|
|
auto gen_fr3 = task.input.at(2) == "#t";
|
|
return jak1::run_build_level(task.input.at(0), task.output.at(0), path_map.output_prefix,
|
|
gen_fr3);
|
|
}
|
|
|
|
BuildLevel2Tool::BuildLevel2Tool() : Tool("build-level2") {}
|
|
|
|
bool BuildLevel2Tool::needs_run(const ToolInput& task, const PathMap& path_map) {
|
|
if (task.input.size() > 3) {
|
|
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
|
}
|
|
auto deps = get_build_level_deps(task.input.at(0));
|
|
auto rerun = task.input.at(1) == "#t";
|
|
std::vector in = {task.input.at(0)};
|
|
return rerun || Tool::needs_run({in, deps, task.output, task.arg}, path_map);
|
|
}
|
|
|
|
bool BuildLevel2Tool::run(const ToolInput& task, const PathMap& path_map) {
|
|
if (task.input.size() > 3) {
|
|
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
|
}
|
|
auto gen_fr3 = task.input.at(2) == "#t";
|
|
return jak2::run_build_level(task.input.at(0), task.output.at(0), path_map.output_prefix,
|
|
gen_fr3);
|
|
}
|
|
|
|
BuildLevel3Tool::BuildLevel3Tool() : Tool("build-level3") {}
|
|
|
|
bool BuildLevel3Tool::needs_run(const ToolInput& task, const PathMap& path_map) {
|
|
if (task.input.size() > 3) {
|
|
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
|
}
|
|
auto deps = get_build_level_deps(task.input.at(0));
|
|
auto rerun = task.input.at(1) == "#t";
|
|
std::vector in = {task.input.at(0)};
|
|
return rerun || Tool::needs_run({in, deps, task.output, task.arg}, path_map);
|
|
}
|
|
|
|
bool BuildLevel3Tool::run(const ToolInput& task, const PathMap& path_map) {
|
|
if (task.input.size() > 3) {
|
|
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
|
}
|
|
auto gen_fr3 = task.input.at(2) == "#t";
|
|
return jak3::run_build_level(task.input.at(0), task.output.at(0), path_map.output_prefix,
|
|
gen_fr3);
|
|
}
|
|
|
|
BuildActorTool::BuildActorTool() : Tool("build-actor") {}
|
|
|
|
bool BuildActorTool::needs_run(const ToolInput& task, const PathMap& path_map) {
|
|
if (task.input.size() > 4) {
|
|
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
|
}
|
|
auto rerun = task.input.at(2) == "#t";
|
|
std::vector<std::string> deps{task.input.at(0)};
|
|
return rerun || Tool::needs_run({deps, deps, task.output, task.arg}, path_map);
|
|
}
|
|
|
|
bool BuildActorTool::run(const ToolInput& task, const PathMap& path_map) {
|
|
(void)path_map;
|
|
if (task.input.size() > 4) {
|
|
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
|
}
|
|
jak1::BuildActorParams params;
|
|
params.gen_collide_mesh = task.input.at(1) == "#t";
|
|
if (task.input.at(3) == "#f") {
|
|
params.texture_bucket = -1;
|
|
} else {
|
|
try {
|
|
params.texture_bucket = static_cast<u8>(std::stoi(task.input.at(3)));
|
|
} catch (std::invalid_argument&) {
|
|
throw std::runtime_error("[build-actor] texture-bucket must be #f or a valid integer.");
|
|
}
|
|
}
|
|
return jak1::run_build_actor(task.input.at(0), task.output.at(0), params);
|
|
}
|
|
|
|
BuildActor2Tool::BuildActor2Tool() : Tool("build-actor2") {}
|
|
|
|
bool BuildActor2Tool::needs_run(const ToolInput& task, const PathMap& path_map) {
|
|
if (task.input.size() > 4) {
|
|
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
|
}
|
|
auto rerun = task.input.at(2) == "#t";
|
|
std::vector<std::string> deps{task.input.at(0)};
|
|
return rerun || Tool::needs_run({deps, deps, task.output, task.arg}, path_map);
|
|
}
|
|
|
|
bool BuildActor2Tool::run(const ToolInput& task, const PathMap& path_map) {
|
|
(void)path_map;
|
|
if (task.input.size() > 4) {
|
|
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
|
}
|
|
jak2::BuildActorParams params;
|
|
params.gen_collide_mesh = task.input.at(1) == "#t";
|
|
if (task.input.at(3) == "#f") {
|
|
params.texture_bucket = -1;
|
|
} else {
|
|
try {
|
|
params.texture_bucket = static_cast<u8>(std::stoi(task.input.at(3)));
|
|
} catch (std::invalid_argument&) {
|
|
throw std::runtime_error("[build-actor2] texture-bucket must be #f or a valid integer.");
|
|
}
|
|
}
|
|
return jak2::run_build_actor(task.input.at(0), task.output.at(0), params);
|
|
}
|
|
|
|
BuildActor3Tool::BuildActor3Tool() : Tool("build-actor3") {}
|
|
|
|
bool BuildActor3Tool::needs_run(const ToolInput& task, const PathMap& path_map) {
|
|
if (task.input.size() > 4) {
|
|
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
|
}
|
|
auto rerun = task.input.at(2) == "#t";
|
|
std::vector<std::string> deps{task.input.at(0)};
|
|
return rerun || Tool::needs_run({deps, deps, task.output, task.arg}, path_map);
|
|
}
|
|
|
|
bool BuildActor3Tool::run(const ToolInput& task, const PathMap& path_map) {
|
|
(void)path_map;
|
|
if (task.input.size() > 4) {
|
|
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
|
}
|
|
jak3::BuildActorParams params;
|
|
params.gen_collide_mesh = task.input.at(1) == "#t";
|
|
if (task.input.at(3) == "#f") {
|
|
params.texture_bucket = -1;
|
|
} else {
|
|
try {
|
|
params.texture_bucket = static_cast<u8>(std::stoi(task.input.at(3)));
|
|
} catch (std::invalid_argument&) {
|
|
throw std::runtime_error("[build-actor3] texture-bucket must be #f or a valid integer.");
|
|
}
|
|
}
|
|
return jak3::run_build_actor(task.input.at(0), task.output.at(0), params);
|
|
} |