jak-project/goalc/main.cpp
ManDude 18ddd1613c
Jak 2 pc subtitle support (#2672)
Adds support for adding custom subtitles to Jak 2 audio. Comes with a
new editor for the new system and format. Compared to the Jak 1 system,
this is much simpler to make an editor for.

Comes with a few subtitles already made as an example.
Cutscenes are not officially supported but you can technically subtitle
those with editor, so please don't right now.

This new system supports multiple subtitles playing at once (even from a
single source!) and will smartly push the subtitles up if there's a
message already playing:

![image](https://github.com/open-goal/jak-project/assets/7569514/033e6374-a05a-4c31-b029-51868153a932)

![image](https://github.com/open-goal/jak-project/assets/7569514/5298aa6d-a183-446e-bdb6-61c4682df917)

Unlike in Jak 1, it will not hide the bottom HUD when subtitles are
active:

![image](https://github.com/open-goal/jak-project/assets/7569514/d466bfc0-55d0-4689-a6e1-b7784b9fff59)

Sadly this leaves us with not much space for the subtitle region (and
the subtitles are shrunk when the minimap is enabled) but when you have
guards and citizens talking all the time, hiding the HUD every time
anyone spoke would get really frustrating.

The subtitle speaker is also color-coded now, because I thought that
would be fun to do.

TODO:
- [x] proper cutscene support.
- [x] merge mode for cutscenes so we don't have to rewrite the script?

---------

Co-authored-by: Hat Kid <6624576+Hat-Kid@users.noreply.github.com>
2023-06-08 01:04:16 +01:00

184 lines
6.1 KiB
C++

#include <cstdio>
#include <regex>
#include "common/log/log.h"
#include "common/repl/nrepl/ReplServer.h"
#include "common/repl/util.h"
#include "common/util/FileUtil.h"
#include "common/util/diff.h"
#include "common/util/string_util.h"
#include "common/util/unicode_util.h"
#include "common/versions/versions.h"
#include "goalc/compiler/Compiler.h"
#include "third-party/CLI11.hpp"
#include "third-party/fmt/color.h"
#include "third-party/fmt/core.h"
void setup_logging() {
lg::set_file(file_util::get_file_path({"log", "compiler.log"}));
lg::set_file_level(lg::level::info);
lg::set_stdout_level(lg::level::info);
lg::set_flush_level(lg::level::info);
lg::initialize();
}
int main(int argc, char** argv) {
ArgumentGuard u8_guard(argc, argv);
bool auto_find_user = false;
std::string cmd = "";
std::string username = "#f";
std::string game = "jak1";
int nrepl_port = -1;
fs::path project_path_override;
// TODO - a lot of these flags could be deprecated and moved into `repl-config.json`
// TODO - auto-find the user if there is only one folder within `user/`
CLI::App app{"OpenGOAL Compiler / REPL"};
app.add_option("-c,--cmd", cmd, "Specify a command to run, no REPL is launched in this mode");
app.add_option("-u,--user", username,
"Specify the username to use for your user profile in 'goal_src/user/'");
app.add_option("-p,--port", nrepl_port,
"Specify the nREPL port. Defaults to 8181 for Jak 1 and 8182 for Jak 2");
app.add_flag("--user-auto", auto_find_user,
"Attempt to automatically deduce the user, overrides '--user'");
app.add_option("-g,--game", game, "The game name: 'jak1' or 'jak2'");
app.add_option("--proj-path", project_path_override,
"Specify the location of the 'data/' folder");
app.validate_positionals();
CLI11_PARSE(app, argc, argv);
GameVersion game_version = game_name_to_version(game);
if (nrepl_port == -1) {
switch (game_version) {
default:
case GameVersion::Jak1:
nrepl_port = 8181;
break;
case GameVersion::Jak2:
nrepl_port = 8182;
break;
}
}
if (!project_path_override.empty()) {
if (!fs::exists(project_path_override)) {
lg::error("Error: project path override '{}' does not exist", project_path_override.string());
return 1;
}
if (!file_util::setup_project_path(project_path_override)) {
lg::error("Could not setup project path!");
return 1;
}
} else if (!file_util::setup_project_path(std::nullopt)) {
return 1;
}
try {
setup_logging();
} catch (const std::exception& e) {
lg::error("Failed to setup logging: {}", e.what());
return 1;
}
lg::info("OpenGOAL Compiler {}.{}", versions::GOAL_VERSION_MAJOR, versions::GOAL_VERSION_MINOR);
// Figure out the username
if (auto_find_user) {
username = REPL::find_repl_username();
}
// Load the user's startup file
auto startup_file = REPL::load_user_startup_file(username, game_version);
// Load the user's REPL config
auto repl_config = REPL::load_repl_config(username, game_version);
// Init Compiler
std::unique_ptr<Compiler> compiler;
std::mutex compiler_mutex;
// if a command is provided on the command line, no REPL just run the compiler on it
try {
if (!cmd.empty()) {
compiler = std::make_unique<Compiler>(game_version);
compiler->run_front_end_on_string(cmd);
return 0;
}
} catch (std::exception& e) {
lg::error("Compiler Fatal Error: {}", e.what());
return 1;
}
// Otherwise, start the REPL normally
ReplStatus status = ReplStatus::OK;
std::function<void()> repl_startup_func = [&]() {
// Run automatic forms if applicable
std::lock_guard<std::mutex> lock(compiler_mutex);
for (const auto& cmd : startup_file.run_before_listen) {
status = compiler->handle_repl_string(cmd);
}
};
// Initialize nREPL server socket
std::function<bool()> shutdown_callback = [&]() { return status == ReplStatus::WANT_EXIT; };
ReplServer repl_server(shutdown_callback, nrepl_port);
bool repl_server_ok = repl_server.init_server();
std::thread nrepl_thread;
// the compiler may throw an exception if it fails to load its standard library.
try {
compiler = std::make_unique<Compiler>(
game_version, std::make_optional(repl_config), username,
std::make_unique<REPL::Wrapper>(username, repl_config, startup_file));
// Start nREPL Server if it spun up successfully
if (repl_server_ok) {
nrepl_thread = std::thread([&]() {
while (!shutdown_callback()) {
auto resp = repl_server.get_msg();
if (resp) {
std::lock_guard<std::mutex> lock(compiler_mutex);
status = compiler->handle_repl_string(resp.value());
// Print out the prompt, just for better UX
compiler->print_to_repl(compiler->get_prompt());
}
std::this_thread::sleep_for(std::chrono::microseconds(50000));
}
});
}
repl_startup_func();
// Poll Terminal
while (status != ReplStatus::WANT_EXIT) {
if (status == ReplStatus::WANT_RELOAD) {
lg::info("Reloading compiler...");
std::lock_guard<std::mutex> lock(compiler_mutex);
if (compiler) {
compiler->save_repl_history();
}
compiler = std::make_unique<Compiler>(
game_version, std::make_optional(repl_config), username,
std::make_unique<REPL::Wrapper>(username, repl_config, startup_file));
status = ReplStatus::OK;
}
// process user input
std::string input_from_stdin = compiler->get_repl_input();
if (!input_from_stdin.empty()) {
// lock, while we compile
std::lock_guard<std::mutex> lock(compiler_mutex);
status = compiler->handle_repl_string(input_from_stdin);
}
}
} catch (std::exception& e) {
lg::error("Compiler Fatal Error: {}", e.what());
status = ReplStatus::WANT_EXIT;
}
// TODO - investigate why there is such a delay when exitting
// Cleanup
if (repl_server_ok) {
repl_server.shutdown_server();
nrepl_thread.join();
}
return 0;
}