mirror of
https://github.com/open-goal/jak-project
synced 2026-05-23 15:02:01 -04:00
e6260e48ab
Adds support for exporting animations for foreground models. It's not perfect and doesn't handle the Jak 2/3 animations very well in some cases (scale can often get messed up, especially for the LZO compressed ones, I have no idea what is going on with the data in those art groups sometimes, so that'll have to be revisited later...), but it does a decent job on Jak 1. Additionally, the `build-actor` tool has also been changed to support setting the `master-art-group-name` and `master-art-group-index` fields to allow for custom art groups to link their animations to a different master art group, which lets you add custom animations to vanilla art groups.
459 lines
17 KiB
C++
459 lines
17 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/format.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() > 8) {
|
|
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
|
}
|
|
auto rerun = task.input.at(2) == "#t";
|
|
std::vector 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() > 8) {
|
|
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
|
}
|
|
jak1::BuildActorParams1 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<s8>(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.");
|
|
}
|
|
}
|
|
params.framerate = std::stof(task.input.at(4));
|
|
if (task.input.at(5) != "#f") {
|
|
params.master_art_group = task.input.at(5);
|
|
}
|
|
auto master_ag_list = m_reader.read_from_string(task.input.at(6));
|
|
// e.g. ((jakb-board-stance 180) (jakb-board-airwalk 181))
|
|
if (!master_ag_list.as_pair()->cdr.is_empty_list()) {
|
|
std::map<std::string, int> master_ag_map;
|
|
goos::for_each_in_list(master_ag_list.as_pair()->cdr.as_pair()->car,
|
|
[&](const goos::Object& o) {
|
|
auto map = o.as_pair();
|
|
auto ja = std::string(map->car.as_symbol().name_ptr);
|
|
auto idx = map->cdr.as_pair()->car.as_int();
|
|
master_ag_map.insert({ja, idx});
|
|
});
|
|
params.master_ag_map = master_ag_map;
|
|
}
|
|
if (task.input.at(7) != "6") {
|
|
params.joint_channel = std::stoi(task.input.at(7));
|
|
}
|
|
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() > 8) {
|
|
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
|
}
|
|
auto rerun = task.input.at(2) == "#t";
|
|
std::vector 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() > 8) {
|
|
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
|
}
|
|
jak2::BuildActorParams2 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<s8>(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.");
|
|
}
|
|
}
|
|
params.framerate = std::stof(task.input.at(4));
|
|
if (task.input.at(5) != "#f") {
|
|
params.master_art_group = task.input.at(5);
|
|
}
|
|
auto master_ag_list = m_reader.read_from_string(task.input.at(6));
|
|
if (!master_ag_list.as_pair()->cdr.is_empty_list()) {
|
|
std::map<std::string, int> master_ag_map;
|
|
goos::for_each_in_list(master_ag_list.as_pair()->cdr.as_pair()->car,
|
|
[&](const goos::Object& o) {
|
|
auto map = o.as_pair();
|
|
auto ja = std::string(map->car.as_symbol().name_ptr);
|
|
auto idx = map->cdr.as_pair()->car.as_int();
|
|
master_ag_map.insert({ja, idx});
|
|
});
|
|
params.master_ag_map = master_ag_map;
|
|
}
|
|
if (task.input.at(7) != "6") {
|
|
params.joint_channel = std::stoi(task.input.at(7));
|
|
}
|
|
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() > 8) {
|
|
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
|
}
|
|
auto rerun = task.input.at(2) == "#t";
|
|
std::vector 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() > 8) {
|
|
throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name()));
|
|
}
|
|
jak3::BuildActorParams3 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<s8>(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.");
|
|
}
|
|
}
|
|
params.framerate = std::stof(task.input.at(4));
|
|
if (task.input.at(5) != "#f") {
|
|
params.master_art_group = task.input.at(5);
|
|
}
|
|
auto master_ag_list = m_reader.read_from_string(task.input.at(6));
|
|
if (!master_ag_list.as_pair()->cdr.is_empty_list()) {
|
|
std::map<std::string, int> master_ag_map;
|
|
goos::for_each_in_list(master_ag_list.as_pair()->cdr.as_pair()->car,
|
|
[&](const goos::Object& o) {
|
|
auto map = o.as_pair();
|
|
auto ja = std::string(map->car.as_symbol().name_ptr);
|
|
auto idx = map->cdr.as_pair()->car.as_int();
|
|
master_ag_map.insert({ja, idx});
|
|
});
|
|
params.master_ag_map = master_ag_map;
|
|
}
|
|
if (task.input.at(7) != "6") {
|
|
params.joint_channel = std::stoi(task.input.at(7));
|
|
}
|
|
return jak3::run_build_actor(task.input.at(0), task.output.at(0), params);
|
|
} |