diff --git a/.vs/launch.vs.json b/.vs/launch.vs.json index 440f0071d0..2aee440c17 100644 --- a/.vs/launch.vs.json +++ b/.vs/launch.vs.json @@ -84,6 +84,13 @@ "name" : "Run - Decompiler - Jak 1", "args" : [ "${workspaceRoot}/decompiler/config/jak1_ntsc_black_label.jsonc", "${workspaceRoot}/iso_data", "${workspaceRoot}/decompiler_out"] }, + { + "type" : "default", + "project" : "CMakeLists.txt", + "projectTarget" : "decompiler.exe (bin\\decompiler.exe)", + "name" : "Run - Decompiler - Jak 1 - Data Only", + "args" : [ "${workspaceRoot}/decompiler/config/jak1_ntsc_black_label.jsonc", "${workspaceRoot}/iso_data", "${workspaceRoot}/decompiler_out", "decompile_code=false"] + }, { "type" : "default", "project" : "CMakeLists.txt", diff --git a/Taskfile.yml b/Taskfile.yml index a2b14b41da..7ed41a9f12 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -47,7 +47,7 @@ tasks: cmds: - cmd: python ./third-party/run-clang-format/run-clang-format.py -r common decompiler game goalc test -i # npm install -g prettier - - cmd: prettier --write ./decompiler/config/jak1_ntsc_black_label/*.jsonc + - cmd: npx prettier --write ./decompiler/config/jak1_ntsc_black_label/*.jsonc ignore_error: true run-game-headless: cmds: diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index c867ac74eb..45e753162f 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -3,7 +3,9 @@ add_library(common cross_os_debug/xdbg.cpp cross_sockets/XSocket.cpp cross_sockets/XSocketServer.cpp + cross_sockets/XSocketClient.cpp custom_data/TFrag3Data.cpp + deserialization/subtitles/subtitles.cpp dma/dma.cpp dma/dma_copy.cpp dma/gs.cpp @@ -17,8 +19,11 @@ add_library(common goos/Reader.cpp goos/TextDB.cpp goos/ReplUtils.cpp - math/geometry.cpp log/log.cpp + math/geometry.cpp + nrepl/ReplClient.cpp + nrepl/ReplServer.cpp + serialization/subtitles/subtitles.cpp type_system/defenum.cpp type_system/deftype.cpp type_system/state.cpp @@ -43,9 +48,7 @@ add_library(common util/print_float.cpp util/FontUtils.cpp util/FrameLimiter.cpp - util/image_loading.cpp - goos/Printer.cpp - goos/PrettyPrinter2.cpp) + util/image_loading.cpp) target_link_libraries(common fmt lzokay replxx libzstd_static) diff --git a/common/cross_sockets/XSocket.cpp b/common/cross_sockets/XSocket.cpp index 4c60c41a06..555cb16cfb 100644 --- a/common/cross_sockets/XSocket.cpp +++ b/common/cross_sockets/XSocket.cpp @@ -34,6 +34,14 @@ int open_socket(int af, int type, int protocol) { #endif } +int connect_socket(int socket, sockaddr* addr, int nameLen) { + int result = connect(socket, addr, nameLen); + if (result == -1) { + return -1; + } + return result; +} + #ifdef __linux int accept_socket(int socket, sockaddr* addr, socklen_t* addrLen) { return accept(socket, addr, addrLen); diff --git a/common/cross_sockets/XSocket.h b/common/cross_sockets/XSocket.h index d73306b518..0e80f35138 100644 --- a/common/cross_sockets/XSocket.h +++ b/common/cross_sockets/XSocket.h @@ -25,6 +25,7 @@ const int TCP_SOCKET_LEVEL = IPPROTO_TCP; #endif int open_socket(int af, int type, int protocol); +int connect_socket(int socket, sockaddr* addr, int nameLen); #ifdef __linux int accept_socket(int socket, sockaddr* addr, socklen_t* addrLen); int select_and_accept_socket(int socket, sockaddr* addr, socklen_t* addrLen, int microSeconds); diff --git a/common/cross_sockets/XSocketClient.cpp b/common/cross_sockets/XSocketClient.cpp new file mode 100644 index 0000000000..e698e8e29e --- /dev/null +++ b/common/cross_sockets/XSocketClient.cpp @@ -0,0 +1,52 @@ +#include "XSocketClient.h" + +#include "common/cross_sockets/XSocket.h" +#include + +#ifdef _WIN32 +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#endif +#include "common/nrepl/ReplServer.h" +#include "third-party/fmt/core.h" + +XSocketClient::XSocketClient(int _tcp_port) { + tcp_port = _tcp_port; +} + +XSocketClient::~XSocketClient() { + disconnect(); + client_socket = -1; +} + +void XSocketClient::disconnect() { + close_socket(client_socket); + client_socket = -1; +} + +bool XSocketClient::connect() { + // Open Socket + client_socket = open_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (client_socket < 0) { + // TODO - log + disconnect(); + return false; + } + + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + addr.sin_port = htons(tcp_port); + + // Connect to server + int result = connect_socket(client_socket, (sockaddr*)&addr, sizeof(addr)); + if (result == -1) { + // TODO - log and close + disconnect(); + return false; + } + + return true; +} diff --git a/common/cross_sockets/XSocketClient.h b/common/cross_sockets/XSocketClient.h new file mode 100644 index 0000000000..10c7b48949 --- /dev/null +++ b/common/cross_sockets/XSocketClient.h @@ -0,0 +1,28 @@ +#pragma once + +#include "common/cross_sockets/XSocket.h" + +#include +#include "common/common_types.h" +#include +#include + +/// @brief A cross platform generic socket client implementation +class XSocketClient { + public: + XSocketClient(int _tcp_port); + ~XSocketClient(); + + XSocketClient(const XSocketClient&) = delete; + XSocketClient& operator=(const XSocketClient&) = delete; + + bool connect(); + void disconnect(); + + bool is_connected() { return client_socket != -1; } + + protected: + int tcp_port; + struct sockaddr_in addr = {}; + int client_socket = -1; +}; diff --git a/common/deserialization/subtitles/subtitles.cpp b/common/deserialization/subtitles/subtitles.cpp new file mode 100644 index 0000000000..f55aebe105 --- /dev/null +++ b/common/deserialization/subtitles/subtitles.cpp @@ -0,0 +1,82 @@ +#include "subtitles.h" + +#include +#include "third-party/fmt/core.h" +#include "third-party/fmt/ranges.h" +#include +#include "common/util/FileUtil.h" +#include "third-party/json.hpp" + +bool write_subtitle_db_to_files(const GameSubtitleDB& db) { + // Write the subtitles out + std::vector completed_banks = {}; + for (const auto& [id, bank] : db.m_banks) { + // If we've done the bank before, skip it + auto it = find(completed_banks.begin(), completed_banks.end(), bank->m_lang_id); + if (it != completed_banks.end()) { + continue; + } + // Check to see if this bank is shared by any other, if so do it at the same time + // and skip it + // This is basically just to deal with US/UK english in a not so hacky way + std::vector banks = {}; + for (const auto& [_id, _bank] : db.m_banks) { + if (_bank->file_path == bank->file_path) { + banks.push_back(_bank->m_lang_id); + completed_banks.push_back(_bank->m_lang_id); + } + } + + std::string file_contents = ""; + file_contents += fmt::format("(language-id {})\n", fmt::join(banks, " ")); + + for (const auto& group_name : db.m_subtitle_groups->m_group_order) { + file_contents += + fmt::format("\n;; -----------------\n;; {}\n;; -----------------\n", group_name); + for (const auto& [scene_name, scene_info] : bank->m_scenes) { + if (scene_info.m_sorting_group != group_name) { + continue; + } + file_contents += fmt::format("\n(\"{}\"", scene_name); + if (scene_info.m_kind == SubtitleSceneKind::Hint) { + file_contents += " :hint 0"; + } else if (scene_info.m_kind == SubtitleSceneKind::HintNamed) { + file_contents += fmt::format(" :hint #x{0:x}", scene_info.m_id); + } + file_contents += "\n"; + for (const auto& line : scene_info.m_lines) { + // Clear screen entries + if (line.line_utf8.empty()) { + file_contents += fmt::format(" ({})\n", line.frame); + } else { + file_contents += fmt::format(" ({}", line.frame); + if (line.offscreen && scene_info.m_kind == SubtitleSceneKind::Movie) { + file_contents += " :offscreen"; + } + file_contents += fmt::format(" \"{}\"", line.speaker_utf8); + // escape quotes + std::string temp = line.line_utf8; + temp = std::regex_replace(temp, std::regex("\""), "\\\""); + file_contents += fmt::format(" \"{}\")\n", temp); + } + } + file_contents += " )\n"; + } + } + + // Commit it to the file + std::string full_path = + (file_util::get_jak_project_dir() / std::filesystem::path(bank->file_path)).string(); + file_util::write_text_file(full_path, file_contents); + } + + // Write the subtitle group info out + nlohmann::json json(db.m_subtitle_groups->m_groups); + json[db.m_subtitle_groups->group_order_key] = nlohmann::json(db.m_subtitle_groups->m_group_order); + std::string file_path = (file_util::get_jak_project_dir() / "game" / "assets" / "jak1" / + "subtitle" / "subtitle-groups.json") + .string(); + file_util::write_text_file(file_path, json.dump(2)); + + return true; +} diff --git a/common/deserialization/subtitles/subtitles.h b/common/deserialization/subtitles/subtitles.h new file mode 100644 index 0000000000..80361bce9e --- /dev/null +++ b/common/deserialization/subtitles/subtitles.h @@ -0,0 +1,5 @@ +#pragma once + +#include "common/serialization/subtitles/subtitles.h" + +bool write_subtitle_db_to_files(const GameSubtitleDB& db); diff --git a/common/nrepl/ReplClient.cpp b/common/nrepl/ReplClient.cpp new file mode 100644 index 0000000000..bbadd67ce8 --- /dev/null +++ b/common/nrepl/ReplClient.cpp @@ -0,0 +1,34 @@ +#include "ReplClient.h" + +#include "common/cross_sockets/XSocket.h" + +#include "third-party/fmt/core.h" +#include "common/versions.h" + +#ifdef _WIN32 +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#endif + +void ReplClient::eval(std::string form) { + if (!is_connected()) { + return; + } + // TODO - split this up into two writes + u32 dataLength = form.length(); + ReplServerHeader header = {dataLength, ReplServerMessageType::EVAL}; + + auto const ptr = reinterpret_cast(&header); + std::vector buffer(ptr, ptr + sizeof header); + + buffer.insert(buffer.end(), form.begin(), form.end()); + + int result = write_to_socket(client_socket, buffer.data(), buffer.size()); + if (result == -1) { + // TODO - log + disconnect(); + } +} diff --git a/common/nrepl/ReplClient.h b/common/nrepl/ReplClient.h new file mode 100644 index 0000000000..2c08154872 --- /dev/null +++ b/common/nrepl/ReplClient.h @@ -0,0 +1,15 @@ +#pragma once + +#include "common/cross_sockets/XSocketClient.h" +#include "ReplServer.h" + +class ReplClient : public XSocketClient { + public: + using XSocketClient::XSocketClient; + virtual ~ReplClient() = default; + + ReplClient& operator=(const ReplClient&) { return *this; } + + // TODO - just void for now :( + void eval(std::string form); +}; diff --git a/goalc/compiler/nrepl/ReplServer.cpp b/common/nrepl/ReplServer.cpp similarity index 100% rename from goalc/compiler/nrepl/ReplServer.cpp rename to common/nrepl/ReplServer.cpp diff --git a/goalc/compiler/nrepl/ReplServer.h b/common/nrepl/ReplServer.h similarity index 93% rename from goalc/compiler/nrepl/ReplServer.h rename to common/nrepl/ReplServer.h index 9a9d322aa2..58759d3b68 100644 --- a/goalc/compiler/nrepl/ReplServer.h +++ b/common/nrepl/ReplServer.h @@ -1,8 +1,8 @@ #pragma once #include "common/cross_sockets/XSocketServer.h" - -#include "goalc/compiler/Compiler.h" +#include +#include enum ReplServerMessageType { PING = 0, EVAL = 10, SHUTDOWN = 20 }; diff --git a/common/serialization/subtitles/subtitles.cpp b/common/serialization/subtitles/subtitles.cpp new file mode 100644 index 0000000000..075a6d496a --- /dev/null +++ b/common/serialization/subtitles/subtitles.cpp @@ -0,0 +1,368 @@ +#include "subtitles.h" +#include "common/goos/ParseHelpers.h" +#include "common/goos/Reader.h" +#include "common/util/FileUtil.h" +#include "third-party/fmt/core.h" +#include "common/util/json_util.h" + +static const std::unordered_map s_text_ver_enum_map = { + {"jak1-v1", GameTextVersion::JAK1_V1}}; + +// TODO - why not just return the inputs instead of passing in an empty one? +void open_text_project(const std::string& kind, + const std::string& filename, + std::unordered_map>& inputs) { + goos::Reader reader; + auto& proj = reader.read_from_file({filename}).as_pair()->cdr.as_pair()->car; + if (!proj.is_pair() || !proj.as_pair()->car.is_symbol() || + proj.as_pair()->car.as_symbol()->name != kind) { + throw std::runtime_error(fmt::format("invalid {} project", kind)); + } + + goos::for_each_in_list(proj.as_pair()->cdr, [&](const goos::Object& o) { + if (!o.is_pair()) { + throw std::runtime_error(fmt::format("invalid entry in {} project", kind)); + } + + auto& ver = o.as_pair()->car.as_symbol()->name; + auto& in = o.as_pair()->cdr.as_pair()->car.as_string()->data; + + inputs[s_text_ver_enum_map.at(ver)].push_back(in); + }); +} + +int64_t get_int(const goos::Object& obj) { + if (obj.is_int()) { + return obj.integer_obj.value; + } + throw std::runtime_error(obj.print() + " was supposed to be an integer, but isn't"); +} + +const goos::Object& car(const goos::Object& x) { + if (!x.is_pair()) { + throw std::runtime_error("invalid pair"); + } + + return x.as_pair()->car; +} + +const goos::Object& cdr(const goos::Object& x) { + if (!x.is_pair()) { + throw std::runtime_error("invalid pair"); + } + + return x.as_pair()->cdr; +} + +std::string get_string(const goos::Object& x) { + if (x.is_string()) { + return x.as_string()->data; + } + throw std::runtime_error(x.print() + " was supposed to be a string, but isn't"); +} + +/*! + * Parse a game text file. + * Information is added to the game text database. + * + * The file should begin with (language-id x y z...) with the given language IDs. + * Each entry should be (id "line for 1st language" "line for 2nd language" ...) + * This adds the text line to each of the specified languages. + */ +void parse_text(const goos::Object& data, GameTextVersion text_ver, GameTextDB& db) { + auto font = get_font_bank(text_ver); + std::vector> banks; + std::string possible_group_name; + + for_each_in_list(data.as_pair()->cdr, [&](const goos::Object& obj) { + if (obj.is_pair()) { + auto& head = car(obj); + if (head.is_symbol() && head.as_symbol()->name == "language-id") { + if (banks.size() != 0) { + throw std::runtime_error("Languages have been set multiple times."); + } + + if (cdr(obj).is_empty_list()) { + throw std::runtime_error("At least one language must be set."); + } + + if (possible_group_name.empty()) { + throw std::runtime_error("Text group must be set before languages."); + } + + for_each_in_list(cdr(obj), [&](const goos::Object& obj) { + auto lang = get_int(obj); + if (!db.bank_exists(possible_group_name, lang)) { + // database has no lang in this group yet + banks.push_back(db.add_bank(possible_group_name, std::make_shared(lang))); + } else { + banks.push_back(db.bank_by_id(possible_group_name, lang)); + } + }); + } else if (head.is_symbol() && head.as_symbol()->name == "group-name") { + if (!possible_group_name.empty()) { + throw std::runtime_error("group-name has been set multiple times."); + } + + possible_group_name = get_string(car(cdr(obj))); + + if (possible_group_name.empty()) { + throw std::runtime_error("invalid group-name."); + } + + if (!cdr(cdr(obj)).is_empty_list()) { + throw std::runtime_error("group-name has too many arguments"); + } + } + + else if (head.is_int()) { + if (banks.size() == 0) { + throw std::runtime_error("At least one language must be set before defining entries."); + } + int i = 0; + int id = head.as_int(); + for_each_in_list(cdr(obj), [&](const goos::Object& entry) { + if (entry.is_string()) { + if (i >= int(banks.size())) { + throw std::runtime_error(fmt::format("Too many strings in text id #x{:x}", id)); + } + + auto line = font->convert_utf8_to_game(entry.as_string()->data); + banks[i++]->set_line(id, line); + } else { + throw std::runtime_error(fmt::format("Non-string value in text id #x{:x}", id)); + } + }); + if (i != int(banks.size())) { + throw std::runtime_error( + fmt::format("Not enough strings specified in text id #x{:x}", id)); + } + } else { + throw std::runtime_error("Invalid game text file entry: " + head.print()); + } + } else { + throw std::runtime_error("Invalid game text file"); + } + }); + if (banks.size() == 0) { + throw std::runtime_error("At least one language must be set."); + } +} + +/*! + * Parse a game subtitle file. + * Information is added to the game subtitles database. + * + * The file should begin with (language-id x y z...) for the given language IDs. + * Each scene should be (scene-name ... ) + * This adds the subtitle to each of the specified languages. + */ +void parse_subtitle(const goos::Object& data, + GameTextVersion text_ver, + GameSubtitleDB& db, + const std::string& file_path) { + auto font = get_font_bank(text_ver); + std::map> banks; + + for_each_in_list(data.as_pair()->cdr, [&](const goos::Object& obj) { + if (obj.is_pair()) { + auto& head = car(obj); + if (head.is_symbol() && head.as_symbol()->name == "language-id") { + if (banks.size() != 0) { + throw std::runtime_error("Languages have been set multiple times."); + } + + if (cdr(obj).is_empty_list()) { + throw std::runtime_error("At least one language must be set."); + } + + for_each_in_list(cdr(obj), [&](const goos::Object& obj) { + auto lang = get_int(obj); + if (!db.bank_exists(lang)) { + // database has no lang yet + banks[lang] = db.add_bank(std::make_shared(lang)); + banks[lang]->file_path = file_path; + } else { + banks[lang] = db.bank_by_id(lang); + banks[lang]->file_path = file_path; + } + }); + } + + else if (head.is_string() || head.is_int()) { + if (banks.size() == 0) { + throw std::runtime_error("At least one language must be set before defining scenes."); + } + auto kind = SubtitleSceneKind::Movie; + int id = 0; + auto entries = cdr(obj); + if (head.is_int()) { + kind = SubtitleSceneKind::Hint; + } else if (car(entries).is_symbol()) { + const auto& parm = car(entries).as_symbol()->name; + if (parm == ":hint") { + entries = cdr(entries); + id = car(entries).as_int(); + kind = SubtitleSceneKind::HintNamed; + } else { + throw std::runtime_error("Unknown parameter for subtitle scene"); + } + entries = cdr(entries); + } + + GameSubtitleSceneInfo scene(kind); + if (kind == SubtitleSceneKind::Movie || kind == SubtitleSceneKind::HintNamed) { + scene.set_name(head.as_string()->data); + } else if (kind == SubtitleSceneKind::Hint) { + id = head.as_int(); + } + scene.set_id(id); + scene.m_sorting_group = db.m_subtitle_groups->find_group(scene.name()); + scene.m_sorting_group_idx = db.m_subtitle_groups->find_group_index(scene.m_sorting_group); + + for_each_in_list(entries, [&](const goos::Object& entry) { + if (entry.is_pair()) { + // expected formats: + // (time ) + // all arguments have default values. the arguments are: + // "speaker" "line" - two strings. one for the speaker's name and one for the actual + // line. speaker can be empty. default is just empty string. + // :offscreen - speaker is offscreen. default is not offscreen. + + if (!car(entry).is_int()) { + throw std::runtime_error("Each entry must start with a timestamp (number)"); + } + + auto time = car(entry).as_int(); + goos::StringObject *speaker = nullptr, *line = nullptr; + bool offscreen = false; + if (scene.kind() == SubtitleSceneKind::Hint || + scene.kind() == SubtitleSceneKind::HintNamed) { + offscreen = true; + } + for_each_in_list(cdr(entry), [&](const goos::Object& arg) { + if (arg.is_string()) { + if (!speaker) { + speaker = arg.as_string(); + } else if (!line) { + line = arg.as_string(); + } else { + throw std::runtime_error("Invalid string in subtitle entry"); + } + } else if (speaker && !line) { + throw std::runtime_error( + "Invalid object in subtitle entry, expecting actual line string after speaker"); + } else if (arg.is_symbol()) { + if (scene.kind() == SubtitleSceneKind::Movie && + arg.as_symbol()->name == ":offscreen") { + offscreen = true; + } else { + throw std::runtime_error( + fmt::format("Unknown parameter {} in subtitle", arg.as_symbol()->name)); + } + } + }); + auto line_utf8 = line ? line->data : ""; + auto line_str = font->convert_utf8_to_game(line_utf8); + auto speaker_utf8 = speaker ? speaker->data : ""; + auto speaker_str = font->convert_utf8_to_game(speaker_utf8); + scene.add_line(time, line_str, line_utf8, speaker_str, speaker_utf8, offscreen); + } else { + throw std::runtime_error("Each entry must be a list"); + } + }); + for (auto& [lang, bank] : banks) { + if (!bank->scene_exists(scene.name())) { + bank->add_scene(scene); + } else { + auto& old_scene = bank->scene_by_name(scene.name()); + old_scene.from_other_scene(scene); + } + } + } else { + throw std::runtime_error("Invalid game subtitles file entry: " + head.print()); + } + } else { + throw std::runtime_error("Invalid game subtitles file"); + } + }); + if (banks.size() == 0) { + throw std::runtime_error("At least one language must be set."); + } +} + +void GameSubtitleGroups::hydrate_from_asset_file() { + std::string file_path = (file_util::get_jak_project_dir() / "game" / "assets" / "jak1" / + "subtitle" / "subtitle-groups.json") + .string(); + auto config_str = file_util::read_text_file(file_path); + auto group_data = parse_commented_json(config_str, file_path); + + for (const auto& [key, val] : group_data.items()) { + try { + if (key == group_order_key) { + m_group_order = val.get>(); + } else { + m_groups[key] = val.get>(); + } + } catch (std::exception& ex) { + fmt::print("Bad subtitle group entry - {} - {}", key, ex.what()); + } + } +} + +std::string GameSubtitleGroups::find_group(const std::string& scene_name) { + for (auto const& [group, scenes] : m_groups) { + for (auto const& name : scenes) { + if (name == scene_name) { + return group; + } + } + } + // Add to the uncategorized group if it wasn't found + m_groups[uncategorized_group].push_back(scene_name); + return uncategorized_group; +} + +int GameSubtitleGroups::find_group_index(const std::string& group_name) { + auto it = find(m_group_order.begin(), m_group_order.end(), group_name); + if (it != m_group_order.end()) { + return it - m_group_order.begin(); + } else { + return m_group_order.size() - 1; + } +} + +void GameSubtitleGroups::remove_scene(const std::string& group_name, + const std::string& scene_name) { + // TODO - validate group_name + m_groups[group_name].erase( + std::remove(m_groups[group_name].begin(), m_groups[group_name].end(), scene_name), + m_groups[group_name].end()); +} +void GameSubtitleGroups::add_scene(const std::string& group_name, const std::string& scene_name) { + // TODO - validate group_name + // TODO - don't add duplicates + m_groups[group_name].push_back(scene_name); +} + +GameSubtitleDB load_subtitle_project() { + // Load the subtitle files + GameSubtitleDB db; + db.m_subtitle_groups = std::make_unique(); + db.m_subtitle_groups->hydrate_from_asset_file(); + goos::Reader reader; + std::unordered_map> inputs; + std::string subtitle_project = + (file_util::get_jak_project_dir() / "game" / "assets" / "game_subtitle.gp").string(); + open_text_project("subtitle", subtitle_project, inputs); + for (auto& [ver, in] : inputs) { + for (auto& filename : in) { + auto code = reader.read_from_file({filename}); + parse_subtitle(code, ver, db, filename); + } + } + return db; +} + +// TODO - write a deserializer, the compiler still can do the compiling! diff --git a/common/serialization/subtitles/subtitles.h b/common/serialization/subtitles/subtitles.h new file mode 100644 index 0000000000..c400cf2cf6 --- /dev/null +++ b/common/serialization/subtitles/subtitles.h @@ -0,0 +1,206 @@ +#pragma once + +#include "common/util/FontUtils.h" +#include "common/util/Assert.h" +#include "common/goos/Object.h" +#include +#include +#include +#include +#include + +/*! + * The text bank contains all lines (accessed with an ID) for a language. + */ +class GameTextBank { + public: + GameTextBank(int lang_id) : m_lang_id(lang_id) {} + + int lang() const { return m_lang_id; } + const std::map& lines() const { return m_lines; } + + bool line_exists(int id) const { return m_lines.find(id) != m_lines.end(); } + std::string line(int id) { return m_lines.at(id); } + void set_line(int id, std::string line) { m_lines[id] = line; } + + private: + int m_lang_id; + std::map m_lines; +}; + +/*! + * The text database contains a text bank for each language for each text group. + * Each text bank contains a list of text lines. Very simple. + */ +class GameTextDB { + public: + const std::unordered_map>>& groups() + const { + return m_banks; + } + const std::map>& banks(std::string group) const { + return m_banks.at(group); + } + + bool bank_exists(std::string group, int id) const { + if (m_banks.find(group) == m_banks.end()) + return false; + return m_banks.at(group).find(id) != m_banks.at(group).end(); + } + + std::shared_ptr add_bank(std::string group, std::shared_ptr bank) { + ASSERT(!bank_exists(group, bank->lang())); + m_banks[group][bank->lang()] = bank; + return bank; + } + std::shared_ptr bank_by_id(std::string group, int id) { + if (!bank_exists(group, id)) { + return nullptr; + } + return m_banks.at(group).at(id); + } + + private: + std::unordered_map>> m_banks; +}; + +/*! + * The subtitle scene info (accessed through the scene name) contains all lines and their timestamps + * and other settings. + */ +enum class SubtitleSceneKind { Invalid = -1, Movie = 0, Hint = 1, HintNamed = 2 }; +class GameSubtitleSceneInfo { + public: + struct SubtitleLine { + SubtitleLine(int frame, + std::string line, + std::string line_utf8, + std::string speaker, + std::string speaker_utf8, + bool offscreen) + : frame(frame), + line(line), + line_utf8(line_utf8), + speaker(speaker), + speaker_utf8(speaker_utf8), + offscreen(offscreen) {} + + int frame; + std::string line; + std::string line_utf8; + std::string speaker; + std::string speaker_utf8; + bool offscreen; + + bool operator<(const SubtitleLine& line) const { return (frame < line.frame); } + }; + + GameSubtitleSceneInfo() {} + GameSubtitleSceneInfo(SubtitleSceneKind kind) : m_kind(kind) {} + + const std::string& name() const { return m_name; } + const std::vector& lines() const { return m_lines; } + int id() const { return m_id; } + SubtitleSceneKind kind() const { return m_kind; } + + void clear_lines() { m_lines.clear(); } + void set_name(const std::string& new_name) { m_name = new_name; } + void set_id(int new_id) { m_id = new_id; } + void from_other_scene(GameSubtitleSceneInfo& scene) { + m_name = scene.name(); + m_lines = scene.lines(); + m_kind = scene.kind(); + m_id = scene.id(); + } + + void add_line(int frame, + std::string line, + std::string line_utf8, + std::string speaker, + std::string speaker_utf8, + bool offscreen) { + m_lines.emplace_back(SubtitleLine(frame, line, line_utf8, speaker, speaker_utf8, offscreen)); + std::sort(m_lines.begin(), m_lines.end()); + } + + std::string m_name; + int m_id; + std::vector m_lines; + SubtitleSceneKind m_kind; + std::string m_sorting_group; + int m_sorting_group_idx; +}; + +/*! + * The subtitle bank contains subtitles for all scenes in a language. + */ +class GameSubtitleBank { + public: + GameSubtitleBank(int lang_id) : m_lang_id(lang_id) {} + + int lang() const { return m_lang_id; } + const std::map& scenes() const { return m_scenes; } + + bool scene_exists(const std::string& name) const { return m_scenes.find(name) != m_scenes.end(); } + GameSubtitleSceneInfo& scene_by_name(const std::string& name) { return m_scenes.at(name); } + void add_scene(GameSubtitleSceneInfo& scene) { + ASSERT(!scene_exists(scene.name())); + m_scenes[scene.name()] = scene; + } + + int m_lang_id; + std::string file_path; + + std::map m_scenes; +}; + +class GameSubtitleGroups { + public: + std::vector m_group_order; + std::map> m_groups; + + void hydrate_from_asset_file(); + std::string find_group(const std::string& scene_name); + int find_group_index(const std::string& group_name); + void remove_scene(const std::string& group_name, const std::string& scene_name); + void add_scene(const std::string& group_name, const std::string& scene_name); + + std::string group_order_key = "_groups"; + std::string uncategorized_group = "uncategorized"; +}; + +/*! + * The subtitles database contains a subtitles bank for each language. + * Each subtitles bank contains a series of subtitle scene infos. + */ +class GameSubtitleDB { + public: + const std::map>& banks() const { return m_banks; } + + bool bank_exists(int id) const { return m_banks.find(id) != m_banks.end(); } + + std::shared_ptr add_bank(std::shared_ptr bank) { + ASSERT(!bank_exists(bank->lang())); + m_banks[bank->lang()] = bank; + return bank; + } + std::shared_ptr bank_by_id(int id) { + if (!bank_exists(id)) { + return nullptr; + } + return m_banks.at(id); + } + + std::map> m_banks; + std::unique_ptr m_subtitle_groups; +}; + +// TODO add docstrings + +void parse_text(const goos::Object& data, GameTextVersion text_ver, GameTextDB& db); +void parse_subtitle(const goos::Object& data, + GameTextVersion text_ver, + GameSubtitleDB& db, + const std::string& file_path); + +GameSubtitleDB load_subtitle_project(); diff --git a/decompiler/config/jak1_ntsc_black_label/art_info.jsonc b/decompiler/config/jak1_ntsc_black_label/art_info.jsonc index 95a48be2ed..9a4a9ab374 100644 --- a/decompiler/config/jak1_ntsc_black_label/art_info.jsonc +++ b/decompiler/config/jak1_ntsc_black_label/art_info.jsonc @@ -9,18 +9,18 @@ // NOTE: it's fine to have a function and its file both in here. the function takes priority. "files": { - "target":"eichar-ag", - "target2":"eichar-ag", - "target-death":"eichar-ag", - "powerups":"eichar-ag" + "target": "eichar-ag", + "target2": "eichar-ag", + "target-death": "eichar-ag", + "powerups": "eichar-ag" }, "functions": { - "(code target-warp-out)":"eichar-ag", - "(code mistycannon-missile-idle)":"sack-ag", - "(code billy-snack-eat)":"farthy-snack-ag", - "(code plunger-lurker-plunge)":"plunger-lurker-ag", - "(code plunger-lurker-flee)":"plunger-lurker-ag", - "(code plunger-lurker-idle)":"plunger-lurker-ag" + "(code target-warp-out)": "eichar-ag", + "(code mistycannon-missile-idle)": "sack-ag", + "(code billy-snack-eat)": "farthy-snack-ag", + "(code plunger-lurker-plunge)": "plunger-lurker-ag", + "(code plunger-lurker-flee)": "plunger-lurker-ag", + "(code plunger-lurker-idle)": "plunger-lurker-ag" } } diff --git a/decompiler/config/jak1_ntsc_black_label/hacks.jsonc b/decompiler/config/jak1_ntsc_black_label/hacks.jsonc index ef152a5e43..df452c7c5b 100644 --- a/decompiler/config/jak1_ntsc_black_label/hacks.jsonc +++ b/decompiler/config/jak1_ntsc_black_label/hacks.jsonc @@ -616,8 +616,6 @@ "generic-no-light-dproc", "generic-envmap-dproc", "generic-tie-convert" - - ], "mips2c_jump_table_functions": { diff --git a/decompiler/config/jak1_ntsc_black_label/import_deps.jsonc b/decompiler/config/jak1_ntsc_black_label/import_deps.jsonc index 840f5769fa..fc6d78ae13 100644 --- a/decompiler/config/jak1_ntsc_black_label/import_deps.jsonc +++ b/decompiler/config/jak1_ntsc_black_label/import_deps.jsonc @@ -1,660 +1,470 @@ { - "jungle-obs": [ - "goal_src/import/maindoor-ag.gc", - "goal_src/import/junglecam-ag.gc", - "goal_src/import/precurbridge-ag.gc", - "goal_src/import/sidedoor-ag.gc", - "goal_src/import/towertop-ag.gc", - "goal_src/import/logtrap-ag.gc", - "goal_src/import/lurkerm-tall-sail-ag.gc", - "goal_src/import/medres-firecanyon-ag.gc", - "goal_src/import/lurkerm-piston-ag.gc", - "goal_src/import/accordian-ag.gc" - ], - "assistant-village2": [ - "goal_src/import/jaws-ag.gc", - "goal_src/import/assistant-village2-ag.gc" - ], - "floating-launcher": [ - "goal_src/import/floating-launcher-ag.gc" - ], - "jungle-mirrors": [ - "goal_src/import/periscope-ag.gc", - "goal_src/import/reflector-mirror-ag.gc" - ], - "citb-drop-plat": [ - "goal_src/import/citb-drop-plat-ag.gc" - ], - "rolling-robber": [ - "goal_src/import/robber-ag.gc" - ], - "balloonlurker": [ - "goal_src/import/balloonlurker-ag.gc" - ], - "racer": [ - "goal_src/import/racer-ag.gc" - ], - "mistycannon": [ - "goal_src/import/sack-ag.gc", - "goal_src/import/mistycannon-ag.gc" - ], - "beach-obs": [ - "goal_src/import/ecoventrock-ag.gc", - "goal_src/import/beachcam-ag.gc", - "goal_src/import/windmill-one-ag.gc", - "goal_src/import/kickrock-ag.gc", - "goal_src/import/harvester-ag.gc", - "goal_src/import/flutflutegg-ag.gc", - "goal_src/import/grottopole-ag.gc", - "goal_src/import/flutflut-ag.gc", - "goal_src/import/bladeassm-ag.gc" - ], - "snow-flutflut-obs": [ - "goal_src/import/snow-button-ag.gc", - "goal_src/import/flutflut-plat-med-ag.gc", - "goal_src/import/flutflut-plat-small-ag.gc", - "goal_src/import/flutflut-plat-large-ag.gc" - ], - "snow-ball": [ - "goal_src/import/snow-ball-ag.gc" - ], - "sun-iris-door": [ - "goal_src/import/sun-iris-door-ag.gc" - ], - "bouncer": [ - "goal_src/import/bounceytarp-ag.gc" - ], - "swamp-blimp": [ - "goal_src/import/swamp-tetherrock-ag.gc", - "goal_src/import/swamp-rope-ag.gc", - "goal_src/import/swamp-tetherrock-explode-ag.gc", - "goal_src/import/precursor-arm-ag.gc", - "goal_src/import/swamp-blimp-ag.gc" - ], - "village-obs": [ - "goal_src/import/windmill-sail-ag.gc", - "goal_src/import/medres-beach3-ag.gc", - "goal_src/import/mayorgears-ag.gc", - "goal_src/import/medres-village11-ag.gc", - "goal_src/import/revcycleprop-ag.gc", - "goal_src/import/medres-jungle2-ag.gc", - "goal_src/import/revcycle-ag.gc", - "goal_src/import/medres-misty-ag.gc", - "goal_src/import/sagesail-ag.gc", - "goal_src/import/windspinner-ag.gc", - "goal_src/import/medres-jungle1-ag.gc", - "goal_src/import/medres-village12-ag.gc", - "goal_src/import/hutlamp-ag.gc", - "goal_src/import/medres-jungle-ag.gc", - "goal_src/import/medres-village13-ag.gc", - "goal_src/import/medres-beach2-ag.gc", - "goal_src/import/villa-starfish-ag.gc", - "goal_src/import/medres-training-ag.gc", - "goal_src/import/medres-beach-ag.gc", - "goal_src/import/reflector-middle-ag.gc", - "goal_src/import/medres-beach1-ag.gc" - ], - "swamp-bat": [ - "goal_src/import/swamp-bat-ag.gc" - ], - "crates": [ - "goal_src/import/crate-ag.gc" - ], - "lurkerpuppy": [ - "goal_src/import/lurkerpuppy-ag.gc" - ], - "light-eco": [ - "goal_src/import/light-eco-ag.gc" - ], - "voicebox": [ - "goal_src/import/speaker-ag.gc" - ], - "green-eco-lurker": [ - "goal_src/import/green-eco-lurker-ag.gc" - ], - "helix-water": [ - "goal_src/import/helix-slide-door-ag.gc", - "goal_src/import/helix-button-ag.gc" - ], - "billy": [ - "goal_src/import/farthy-snack-ag.gc", - "goal_src/import/billy-ag.gc", - "goal_src/import/billy-sidekick-ag.gc" - ], - "snow-ram": [ - "goal_src/import/ram-ag.gc" - ], - "blocking-plane": [ - "goal_src/import/ef-plane-ag.gc" - ], - "target-util": [ - "goal_src/import/eichar-ag.gc" - ], - "beach-rocks": [ - "goal_src/import/lrocklrg-ag.gc" - ], - "sunken-obs": [ - "goal_src/import/seaweed-ag.gc", - "goal_src/import/sunkencam-ag.gc", - "goal_src/import/side-to-side-plat-ag.gc" - ], - "qbert-plat": [ - "goal_src/import/qbert-plat-ag.gc", - "goal_src/import/qbert-plat-on-ag.gc" - ], - "sun-exit-chamber": [ - "goal_src/import/exit-chamber-ag.gc", - "goal_src/import/blue-eco-charger-ag.gc", - "goal_src/import/blue-eco-charger-orb-ag.gc" - ], - "mother-spider": [ - "goal_src/import/mother-spider-ag.gc" - ], - "darkvine": [ - "goal_src/import/darkvine-ag.gc" - ], - "sidekick": [ - "goal_src/import/sidekick-ag.gc" - ], - "kermit": [ - "goal_src/import/kermit-ag.gc" - ], - "firecanyon-obs": [ - "goal_src/import/crate-darkeco-cluster-ag.gc", - "goal_src/import/spike-ag.gc" - ], - "orbit-plat": [ - "goal_src/import/orbit-plat-ag.gc", - "goal_src/import/orbit-plat-bottom-ag.gc" - ], - "fishermans-boat": [ - "goal_src/import/evilbro-ag.gc", - "goal_src/import/fishermans-boat-ag.gc", - "goal_src/import/evilsis-ag.gc" - ], - "driller-lurker": [ - "goal_src/import/driller-lurker-ag.gc" - ], - "sidekick-human": [ - "goal_src/import/darkecocan-ag.gc", - "goal_src/import/sidekick-human-ag.gc", - "goal_src/import/evilbro-ag.gc", - "goal_src/import/evilsis-ag.gc" - ], - "misty-conveyor": [ - "goal_src/import/keg-conveyor-ag.gc", - "goal_src/import/keg-conveyor-paddle-ag.gc", - "goal_src/import/keg-ag.gc" - ], - "snow-ram-boss": [ - "goal_src/import/ram-boss-ag.gc" - ], - "junglefish": [ - "goal_src/import/junglefish-ag.gc" - ], - "swamp-obs": [ - "goal_src/import/swamp-rock-ag.gc", - "goal_src/import/swamp-spike-ag.gc", - "goal_src/import/swampcam-ag.gc", - "goal_src/import/tar-plat-ag.gc", - "goal_src/import/balance-plat-ag.gc" - ], - "snow-bumper": [ - "goal_src/import/snow-bumper-ag.gc" - ], - "darkcave-obs": [ - "goal_src/import/cavecrystal-ag.gc" - ], - "junglesnake": [ - "goal_src/import/junglesnake-ag.gc" - ], - "evilbro": [ - "goal_src/import/evilbro-ag.gc", - "goal_src/import/evilsis-ag.gc" - ], - "bully": [ - "goal_src/import/bully-ag.gc" - ], - "square-platform": [ - "goal_src/import/square-platform-ag.gc" - ], - "dark-crystal": [ - "goal_src/import/dark-crystal-ag.gc" - ], - "sage-village3": [ - "goal_src/import/evilsis-village3-ag.gc", - "goal_src/import/sage-village3-ag.gc", - "goal_src/import/evilbro-village3-ag.gc" - ], - "yakow": [ - "goal_src/import/village1cam-ag.gc", - "goal_src/import/yakow-ag.gc" - ], - "plat-button": [ - "goal_src/import/plat-button-ag.gc" - ], - "hud-classes": [ - "goal_src/import/fuelcell-naked-ag.gc" - ], - "misty-obs": [ - "goal_src/import/breakaway-right-ag.gc", - "goal_src/import/boatpaddle-ag.gc", - "goal_src/import/breakaway-mid-ag.gc", - "goal_src/import/mis-bone-platform-ag.gc", - "goal_src/import/breakaway-left-ag.gc", - "goal_src/import/windturbine-ag.gc", - "goal_src/import/mistycam-ag.gc", - "goal_src/import/mis-bone-bridge-ag.gc" - ], - "rolling-obs": [ - "goal_src/import/rollingcam-ag.gc", - "goal_src/import/pusher-ag.gc", - "goal_src/import/happy-plant-ag.gc", - "goal_src/import/dark-plant-ag.gc", - "goal_src/import/rolling-start-ag.gc" - ], - "aphid": [ - "goal_src/import/aphid-lurker-ag.gc" - ], - "plat-eco": [ - "goal_src/import/plat-eco-ag.gc" - ], - "assistant": [ - "goal_src/import/assistant-ag.gc" - ], - "flutflut-bluehut": [ - "goal_src/import/flutflut-bluehut-ag.gc" - ], - "explorer": [ - "goal_src/import/explorer-ag.gc" - ], - "rolling-lightning-mole": [ - "goal_src/import/lightning-mole-ag.gc" - ], - "plat": [ - "goal_src/import/plat-sunken-ag.gc", - "goal_src/import/plat-ag.gc", - "goal_src/import/plat-jungleb-ag.gc" - ], - "flutflut": [ - "goal_src/import/flut-saddle-ag.gc" - ], - "training-obs": [ - "goal_src/import/scarecrow-b-ag.gc", - "goal_src/import/pontoonfive-ag.gc", - "goal_src/import/trainingcam-ag.gc", - "goal_src/import/scarecrow-a-ag.gc", - "goal_src/import/jng-iris-door-ag.gc" - ], - "snow-obs": [ - "goal_src/import/snowcam-ag.gc", - "goal_src/import/snow-fort-gate-ag.gc", - "goal_src/import/snow-eggtop-ag.gc", - "goal_src/import/snow-spatula-ag.gc", - "goal_src/import/snow-switch-ag.gc", - "goal_src/import/snow-gears-ag.gc", - "goal_src/import/snowpusher-ag.gc", - "goal_src/import/snow-log-ag.gc" - ], - "citadel-obs": [ - "goal_src/import/citb-generator-ag.gc", - "goal_src/import/citb-launcher-ag.gc", - "goal_src/import/citb-button-ag.gc", - "goal_src/import/citadelcam-ag.gc", - "goal_src/import/citb-hose-ag.gc", - "goal_src/import/citb-robotboss-ag.gc", - "goal_src/import/citb-coil-ag.gc", - "goal_src/import/citb-arm-shoulder-ag.gc", - "goal_src/import/citb-iris-door-ag.gc", - "goal_src/import/citb-disc-ag.gc", - "goal_src/import/citb-arm-ag.gc" - ], - "citadel-sages": [ - "goal_src/import/green-sagecage-ag.gc", - "goal_src/import/yellowsage-ag.gc", - "goal_src/import/redsage-ag.gc", - "goal_src/import/evilbro-citadel-ag.gc", - "goal_src/import/evilsis-citadel-ag.gc", - "goal_src/import/citb-sagecage-ag.gc", - "goal_src/import/bluesage-ag.gc" - ], - "lurkercrab": [ - "goal_src/import/lurkercrab-ag.gc" - ], - "muse": [ - "goal_src/import/muse-ag.gc" - ], - "puffer": [ - "goal_src/import/puffer-ag.gc" - ], - "robotboss-misc": [ - "goal_src/import/silodoor-ag.gc", - "goal_src/import/ecoclaw-ag.gc", - "goal_src/import/finalbosscam-ag.gc" - ], - "sage-finalboss": [ - "goal_src/import/plat-eco-finalboss-ag.gc", - "goal_src/import/green-sagecage-ag.gc", - "goal_src/import/jak-white-ag.gc", - "goal_src/import/robotboss-cinematic-ag.gc" - ], - "target-racer-h": [ - "goal_src/import/balloon-ag.gc" - ], - "gambler": [ - "goal_src/import/gambler-ag.gc" - ], - "lavatube-obs": [ - "goal_src/import/lavafallsewerb-ag.gc", - "goal_src/import/lavashortcut-ag.gc", - "goal_src/import/lavabase-ag.gc", - "goal_src/import/lavafallsewera-ag.gc", - "goal_src/import/chainmine-ag.gc", - "goal_src/import/lavafall-ag.gc", - "goal_src/import/lavaballoon-ag.gc", - "goal_src/import/darkecobarrel-ag.gc", - "goal_src/import/lavayellowtarp-ag.gc" - ], - "assistant-firecanyon": [ - "goal_src/import/assistant-firecanyon-ag.gc" - ], - "sharkey": [ - "goal_src/import/sharkey-ag.gc" - ], - "bonelurker": [ - "goal_src/import/bonelurker-ag.gc" - ], - "pelican": [ - "goal_src/import/pelican-ag.gc" - ], - "warrior": [ - "goal_src/import/warrior-ag.gc" - ], - "maincave-obs": [ - "goal_src/import/caveelevator-ag.gc", - "goal_src/import/cavespatulatwo-ag.gc", - "goal_src/import/cavetrapdoor-ag.gc", - "goal_src/import/maincavecam-ag.gc", - "goal_src/import/cavecrusher-ag.gc", - "goal_src/import/cavespatula-darkcave-ag.gc" - ], - "fisher": [ - "goal_src/import/fish-net-ag.gc", - "goal_src/import/fisher-ag.gc", - "goal_src/import/catch-fisha-ag.gc", - "goal_src/import/catch-fishb-ag.gc", - "goal_src/import/catch-fishc-ag.gc" - ], - "villagep-obs": [ - "goal_src/import/warp-gate-switch-ag.gc", - "goal_src/import/village-cam-ag.gc" - ], - "citb-bunny": [ - "goal_src/import/citb-bunny-ag.gc" - ], - "flying-lurker": [ - "goal_src/import/ogrecam-ag.gc", - "goal_src/import/plunger-lurker-ag.gc", - "goal_src/import/flying-lurker-ag.gc" - ], - "shover": [ - "goal_src/import/shover-ag.gc" - ], - "ice-cube": [ - "goal_src/import/ice-cube-ag.gc", - "goal_src/import/ice-cube-break-ag.gc" - ], - "baby-spider": [ - "goal_src/import/baby-spider-ag.gc" - ], - "minecart": [ - "goal_src/import/minecartsteel-ag.gc" - ], - "bird-lady": [ - "goal_src/import/bird-lady-ag.gc" - ], - "spider-egg": [ - "goal_src/import/spider-egg-ag.gc" - ], - "wall-plat": [ - "goal_src/import/wall-plat-ag.gc" - ], - "oracle": [ - "goal_src/import/oracle-ag.gc" - ], - "basebutton": [ - "goal_src/import/generic-button-ag.gc" - ], - "miners": [ - "goal_src/import/minershort-ag.gc", - "goal_src/import/cavegem-ag.gc", - "goal_src/import/minertall-ag.gc" - ], - "farmer": [ - "goal_src/import/farmer-ag.gc" - ], - "rolling-race-ring": [ - "goal_src/import/race-ring-ag.gc" - ], - "quicksandlurker": [ - "goal_src/import/quicksandlurker-ag.gc" - ], - "misty-warehouse": [ - "goal_src/import/silostep-ag.gc", - "goal_src/import/rounddoor-ag.gc" - ], - "yeti": [ - "goal_src/import/yeti-ag.gc" - ], - "hopper": [ - "goal_src/import/hopper-ag.gc" - ], - "target-death": [ - "goal_src/import/deathcam-ag.gc" - ], - "spiderwebs": [ - "goal_src/import/spiderwebs-ag.gc" - ], - "misty-teetertotter": [ - "goal_src/import/teetertotter-ag.gc" - ], - "village2-obs": [ - "goal_src/import/pontoonfive-ag.gc", - "goal_src/import/allpontoons-ag.gc", - "goal_src/import/medres-village2-ag.gc", - "goal_src/import/exit-chamber-dummy-ag.gc", - "goal_src/import/village2cam-ag.gc", - "goal_src/import/fireboulder-ag.gc", - "goal_src/import/ogreboss-village2-ag.gc", - "goal_src/import/ceilingflag-ag.gc", - "goal_src/import/medres-rolling1-ag.gc", - "goal_src/import/medres-rolling-ag.gc", - "goal_src/import/pontoonten-ag.gc" - ], - "plant-boss": [ - "goal_src/import/plant-boss-ag.gc" - ], - "snow-bunny": [ - "goal_src/import/snow-bunny-ag.gc" - ], - "sunken-elevator": [ - "goal_src/import/sunken-elevator-ag.gc" - ], - "sage": [ - "goal_src/import/sage-ag.gc" - ], - "robotboss-h": [ - "goal_src/import/robotboss-ag.gc" - ], - "geologist": [ - "goal_src/import/geologist-ag.gc" - ], - "village3-obs": [ - "goal_src/import/medres-ogre-ag.gc", - "goal_src/import/medres-finalboss-ag.gc", - "goal_src/import/pistons-ag.gc", - "goal_src/import/gondola-ag.gc", - "goal_src/import/medres-ogre2-ag.gc", - "goal_src/import/medres-ogre3-ag.gc", - "goal_src/import/gondolacables-ag.gc" - ], - "double-lurker": [ - "goal_src/import/double-lurker-ag.gc", - "goal_src/import/double-lurker-top-ag.gc" - ], - "ogreboss": [ - "goal_src/import/ogreboss-ag.gc" - ], - "swamp-rat": [ - "goal_src/import/swamp-rat-ag.gc" - ], - "sculptor": [ - "goal_src/import/sculptor-muse-ag.gc", - "goal_src/import/sculptor-ag.gc" - ], - "seagull": [ - "goal_src/import/seagull-ag.gc" - ], - "mayor": [ - "goal_src/import/mayor-ag.gc" - ], - "final-door": [ - "goal_src/import/power-left-ag.gc", - "goal_src/import/power-right-ag.gc", - "goal_src/import/powercellalt-ag.gc" - ], - "assistant-lavatube": [ - "goal_src/import/assistant-lavatube-start-ag.gc" - ], - "launcherdoor": [ - "goal_src/import/launcherdoor-maincave-ag.gc", - "goal_src/import/launcherdoor-ag.gc" - ], - "title-obs": [ - "goal_src/import/logo-cam-ag.gc", - "goal_src/import/logo-black-ag.gc", - "goal_src/import/logo-ag.gc", - "goal_src/import/logo-volumes-ag.gc", - "goal_src/import/ndi-cam-ag.gc", - "goal_src/import/ndi-ag.gc", - "goal_src/import/ndi-volumes-ag.gc" - ], - "whirlpool": [ - "goal_src/import/whirlpool-ag.gc" - ], - "lurkerworm": [ - "goal_src/import/lurkerworm-ag.gc" - ], - "collectables": [ - "goal_src/import/buzzer-ag.gc", - "goal_src/import/ecovalve-ag.gc", - "goal_src/import/money-ag.gc", - "goal_src/import/fuel-cell-ag.gc" - ], - "water-anim": [ - "goal_src/import/water-anim-maincave-ag.gc", - "goal_src/import/water-anim-village3-ag.gc", - "goal_src/import/water-anim-finalboss-ag.gc", - "goal_src/import/water-anim-maincave-water-ag.gc", - "goal_src/import/water-anim-sunken-ag.gc", - "goal_src/import/water-anim-lavatube-ag.gc", - "goal_src/import/water-anim-robocave-ag.gc", - "goal_src/import/water-anim-jungle-ag.gc", - "goal_src/import/water-anim-ogre-ag.gc", - "goal_src/import/water-anim-training-ag.gc", - "goal_src/import/water-anim-darkcave-ag.gc", - "goal_src/import/water-anim-village1-ag.gc", - "goal_src/import/water-anim-rolling-ag.gc", - "goal_src/import/water-anim-misty-ag.gc", - "goal_src/import/water-anim-sunken-dark-eco-ag.gc", - "goal_src/import/water-anim-village2-ag.gc" - ], - "ropebridge": [ - "goal_src/import/vil3-bridge-36-ag.gc", - "goal_src/import/ropebridge-36-ag.gc", - "goal_src/import/ropebridge-32-ag.gc", - "goal_src/import/ropebridge-52-ag.gc", - "goal_src/import/snow-bridge-36-ag.gc", - "goal_src/import/ropebridge-70-ag.gc" - ], - "robotboss": [ - "goal_src/import/robotboss-redeco-ag.gc", - "goal_src/import/robotboss-blueeco-ag.gc", - "goal_src/import/robotboss-yelloweco-ag.gc" - ], - "steam-cap": [ - "goal_src/import/steam-cap-ag.gc" - ], - "ogre-obs": [ - "goal_src/import/ogre-step-ag.gc", - "goal_src/import/tntbarrel-ag.gc", - "goal_src/import/ogre-bridge-ag.gc", - "goal_src/import/shortcut-boulder-ag.gc", - "goal_src/import/medres-snow-ag.gc", - "goal_src/import/ogre-bridgeend-ag.gc", - "goal_src/import/ogre-isle-ag.gc" - ], - "assistant-village3": [ - "goal_src/import/assistant-village3-ag.gc" - ], - "babak": [ - "goal_src/import/babak-ag.gc" - ], - "lavatube-energy": [ - "goal_src/import/energyball-ag.gc", - "goal_src/import/energybase-ag.gc", - "goal_src/import/energyhub-ag.gc", - "goal_src/import/energyarm-ag.gc", - "goal_src/import/energydoor-ag.gc" - ], - "mother-spider-egg": [ - "goal_src/import/spider-egg-ag.gc" - ], - "plat-flip": [ - "goal_src/import/plat-flip-ag.gc" - ], - "assistant-citadel": [ - "goal_src/import/assistant-lavatube-end-ag.gc" - ], - "bird-lady-beach": [ - "goal_src/import/bird-lady-beach-ag.gc" - ], - "orb-cache": [ - "goal_src/import/orb-cache-top-ag.gc" - ], - "robotboss-weapon": [ - "goal_src/import/darkecobomb-ag.gc", - "goal_src/import/greenshot-ag.gc", - "goal_src/import/redring-ag.gc" - ], - "citb-plat": [ - "goal_src/import/citb-exit-plat-ag.gc", - "goal_src/import/plat-eco-citb-ag.gc", - "goal_src/import/plat-citb-ag.gc", - "goal_src/import/citb-stopbox-ag.gc", - "goal_src/import/citb-firehose-ag.gc", - "goal_src/import/citb-rotatebox-ag.gc", - "goal_src/import/citb-chain-plat-ag.gc", - "goal_src/import/citb-donut-ag.gc" - ], - "gnawer": [ - "goal_src/import/gnawer-ag.gc" - ], - "jungleb-obs": [ - "goal_src/import/eggtop-ag.gc", - "goal_src/import/jng-iris-door-ag.gc" - ], - "swamp-rat-nest": [ - "goal_src/import/swamp-rat-nest-ag.gc" - ], - "wedge-plats": [ - "goal_src/import/wedge-plat-outer-ag.gc", - "goal_src/import/wedge-plat-ag.gc" - ], - "sunken-fish": [ - "goal_src/import/sunkenfisha-ag.gc" - ], - "sage-bluehut": [ - "goal_src/import/sage-bluehut-ag.gc" - ] + "jungle-obs": [ + "goal_src/import/maindoor-ag.gc", + "goal_src/import/junglecam-ag.gc", + "goal_src/import/precurbridge-ag.gc", + "goal_src/import/sidedoor-ag.gc", + "goal_src/import/towertop-ag.gc", + "goal_src/import/logtrap-ag.gc", + "goal_src/import/lurkerm-tall-sail-ag.gc", + "goal_src/import/medres-firecanyon-ag.gc", + "goal_src/import/lurkerm-piston-ag.gc", + "goal_src/import/accordian-ag.gc" + ], + "assistant-village2": [ + "goal_src/import/jaws-ag.gc", + "goal_src/import/assistant-village2-ag.gc" + ], + "floating-launcher": ["goal_src/import/floating-launcher-ag.gc"], + "jungle-mirrors": [ + "goal_src/import/periscope-ag.gc", + "goal_src/import/reflector-mirror-ag.gc" + ], + "citb-drop-plat": ["goal_src/import/citb-drop-plat-ag.gc"], + "rolling-robber": ["goal_src/import/robber-ag.gc"], + "balloonlurker": ["goal_src/import/balloonlurker-ag.gc"], + "racer": ["goal_src/import/racer-ag.gc"], + "mistycannon": [ + "goal_src/import/sack-ag.gc", + "goal_src/import/mistycannon-ag.gc" + ], + "beach-obs": [ + "goal_src/import/ecoventrock-ag.gc", + "goal_src/import/beachcam-ag.gc", + "goal_src/import/windmill-one-ag.gc", + "goal_src/import/kickrock-ag.gc", + "goal_src/import/harvester-ag.gc", + "goal_src/import/flutflutegg-ag.gc", + "goal_src/import/grottopole-ag.gc", + "goal_src/import/flutflut-ag.gc", + "goal_src/import/bladeassm-ag.gc" + ], + "snow-flutflut-obs": [ + "goal_src/import/snow-button-ag.gc", + "goal_src/import/flutflut-plat-med-ag.gc", + "goal_src/import/flutflut-plat-small-ag.gc", + "goal_src/import/flutflut-plat-large-ag.gc" + ], + "snow-ball": ["goal_src/import/snow-ball-ag.gc"], + "sun-iris-door": ["goal_src/import/sun-iris-door-ag.gc"], + "bouncer": ["goal_src/import/bounceytarp-ag.gc"], + "swamp-blimp": [ + "goal_src/import/swamp-tetherrock-ag.gc", + "goal_src/import/swamp-rope-ag.gc", + "goal_src/import/swamp-tetherrock-explode-ag.gc", + "goal_src/import/precursor-arm-ag.gc", + "goal_src/import/swamp-blimp-ag.gc" + ], + "village-obs": [ + "goal_src/import/windmill-sail-ag.gc", + "goal_src/import/medres-beach3-ag.gc", + "goal_src/import/mayorgears-ag.gc", + "goal_src/import/medres-village11-ag.gc", + "goal_src/import/revcycleprop-ag.gc", + "goal_src/import/medres-jungle2-ag.gc", + "goal_src/import/revcycle-ag.gc", + "goal_src/import/medres-misty-ag.gc", + "goal_src/import/sagesail-ag.gc", + "goal_src/import/windspinner-ag.gc", + "goal_src/import/medres-jungle1-ag.gc", + "goal_src/import/medres-village12-ag.gc", + "goal_src/import/hutlamp-ag.gc", + "goal_src/import/medres-jungle-ag.gc", + "goal_src/import/medres-village13-ag.gc", + "goal_src/import/medres-beach2-ag.gc", + "goal_src/import/villa-starfish-ag.gc", + "goal_src/import/medres-training-ag.gc", + "goal_src/import/medres-beach-ag.gc", + "goal_src/import/reflector-middle-ag.gc", + "goal_src/import/medres-beach1-ag.gc" + ], + "swamp-bat": ["goal_src/import/swamp-bat-ag.gc"], + "crates": ["goal_src/import/crate-ag.gc"], + "lurkerpuppy": ["goal_src/import/lurkerpuppy-ag.gc"], + "light-eco": ["goal_src/import/light-eco-ag.gc"], + "voicebox": ["goal_src/import/speaker-ag.gc"], + "green-eco-lurker": ["goal_src/import/green-eco-lurker-ag.gc"], + "helix-water": [ + "goal_src/import/helix-slide-door-ag.gc", + "goal_src/import/helix-button-ag.gc" + ], + "billy": [ + "goal_src/import/farthy-snack-ag.gc", + "goal_src/import/billy-ag.gc", + "goal_src/import/billy-sidekick-ag.gc" + ], + "snow-ram": ["goal_src/import/ram-ag.gc"], + "blocking-plane": ["goal_src/import/ef-plane-ag.gc"], + "target-util": ["goal_src/import/eichar-ag.gc"], + "beach-rocks": ["goal_src/import/lrocklrg-ag.gc"], + "sunken-obs": [ + "goal_src/import/seaweed-ag.gc", + "goal_src/import/sunkencam-ag.gc", + "goal_src/import/side-to-side-plat-ag.gc" + ], + "qbert-plat": [ + "goal_src/import/qbert-plat-ag.gc", + "goal_src/import/qbert-plat-on-ag.gc" + ], + "sun-exit-chamber": [ + "goal_src/import/exit-chamber-ag.gc", + "goal_src/import/blue-eco-charger-ag.gc", + "goal_src/import/blue-eco-charger-orb-ag.gc" + ], + "mother-spider": ["goal_src/import/mother-spider-ag.gc"], + "darkvine": ["goal_src/import/darkvine-ag.gc"], + "sidekick": ["goal_src/import/sidekick-ag.gc"], + "kermit": ["goal_src/import/kermit-ag.gc"], + "firecanyon-obs": [ + "goal_src/import/crate-darkeco-cluster-ag.gc", + "goal_src/import/spike-ag.gc" + ], + "orbit-plat": [ + "goal_src/import/orbit-plat-ag.gc", + "goal_src/import/orbit-plat-bottom-ag.gc" + ], + "fishermans-boat": [ + "goal_src/import/evilbro-ag.gc", + "goal_src/import/fishermans-boat-ag.gc", + "goal_src/import/evilsis-ag.gc" + ], + "driller-lurker": ["goal_src/import/driller-lurker-ag.gc"], + "sidekick-human": [ + "goal_src/import/darkecocan-ag.gc", + "goal_src/import/sidekick-human-ag.gc", + "goal_src/import/evilbro-ag.gc", + "goal_src/import/evilsis-ag.gc" + ], + "misty-conveyor": [ + "goal_src/import/keg-conveyor-ag.gc", + "goal_src/import/keg-conveyor-paddle-ag.gc", + "goal_src/import/keg-ag.gc" + ], + "snow-ram-boss": ["goal_src/import/ram-boss-ag.gc"], + "junglefish": ["goal_src/import/junglefish-ag.gc"], + "swamp-obs": [ + "goal_src/import/swamp-rock-ag.gc", + "goal_src/import/swamp-spike-ag.gc", + "goal_src/import/swampcam-ag.gc", + "goal_src/import/tar-plat-ag.gc", + "goal_src/import/balance-plat-ag.gc" + ], + "snow-bumper": ["goal_src/import/snow-bumper-ag.gc"], + "darkcave-obs": ["goal_src/import/cavecrystal-ag.gc"], + "junglesnake": ["goal_src/import/junglesnake-ag.gc"], + "evilbro": ["goal_src/import/evilbro-ag.gc", "goal_src/import/evilsis-ag.gc"], + "bully": ["goal_src/import/bully-ag.gc"], + "square-platform": ["goal_src/import/square-platform-ag.gc"], + "dark-crystal": ["goal_src/import/dark-crystal-ag.gc"], + "sage-village3": [ + "goal_src/import/evilsis-village3-ag.gc", + "goal_src/import/sage-village3-ag.gc", + "goal_src/import/evilbro-village3-ag.gc" + ], + "yakow": ["goal_src/import/village1cam-ag.gc", "goal_src/import/yakow-ag.gc"], + "plat-button": ["goal_src/import/plat-button-ag.gc"], + "hud-classes": ["goal_src/import/fuelcell-naked-ag.gc"], + "misty-obs": [ + "goal_src/import/breakaway-right-ag.gc", + "goal_src/import/boatpaddle-ag.gc", + "goal_src/import/breakaway-mid-ag.gc", + "goal_src/import/mis-bone-platform-ag.gc", + "goal_src/import/breakaway-left-ag.gc", + "goal_src/import/windturbine-ag.gc", + "goal_src/import/mistycam-ag.gc", + "goal_src/import/mis-bone-bridge-ag.gc" + ], + "rolling-obs": [ + "goal_src/import/rollingcam-ag.gc", + "goal_src/import/pusher-ag.gc", + "goal_src/import/happy-plant-ag.gc", + "goal_src/import/dark-plant-ag.gc", + "goal_src/import/rolling-start-ag.gc" + ], + "aphid": ["goal_src/import/aphid-lurker-ag.gc"], + "plat-eco": ["goal_src/import/plat-eco-ag.gc"], + "assistant": ["goal_src/import/assistant-ag.gc"], + "flutflut-bluehut": ["goal_src/import/flutflut-bluehut-ag.gc"], + "explorer": ["goal_src/import/explorer-ag.gc"], + "rolling-lightning-mole": ["goal_src/import/lightning-mole-ag.gc"], + "plat": [ + "goal_src/import/plat-sunken-ag.gc", + "goal_src/import/plat-ag.gc", + "goal_src/import/plat-jungleb-ag.gc" + ], + "flutflut": ["goal_src/import/flut-saddle-ag.gc"], + "training-obs": [ + "goal_src/import/scarecrow-b-ag.gc", + "goal_src/import/pontoonfive-ag.gc", + "goal_src/import/trainingcam-ag.gc", + "goal_src/import/scarecrow-a-ag.gc", + "goal_src/import/jng-iris-door-ag.gc" + ], + "snow-obs": [ + "goal_src/import/snowcam-ag.gc", + "goal_src/import/snow-fort-gate-ag.gc", + "goal_src/import/snow-eggtop-ag.gc", + "goal_src/import/snow-spatula-ag.gc", + "goal_src/import/snow-switch-ag.gc", + "goal_src/import/snow-gears-ag.gc", + "goal_src/import/snowpusher-ag.gc", + "goal_src/import/snow-log-ag.gc" + ], + "citadel-obs": [ + "goal_src/import/citb-generator-ag.gc", + "goal_src/import/citb-launcher-ag.gc", + "goal_src/import/citb-button-ag.gc", + "goal_src/import/citadelcam-ag.gc", + "goal_src/import/citb-hose-ag.gc", + "goal_src/import/citb-robotboss-ag.gc", + "goal_src/import/citb-coil-ag.gc", + "goal_src/import/citb-arm-shoulder-ag.gc", + "goal_src/import/citb-iris-door-ag.gc", + "goal_src/import/citb-disc-ag.gc", + "goal_src/import/citb-arm-ag.gc" + ], + "citadel-sages": [ + "goal_src/import/green-sagecage-ag.gc", + "goal_src/import/yellowsage-ag.gc", + "goal_src/import/redsage-ag.gc", + "goal_src/import/evilbro-citadel-ag.gc", + "goal_src/import/evilsis-citadel-ag.gc", + "goal_src/import/citb-sagecage-ag.gc", + "goal_src/import/bluesage-ag.gc" + ], + "lurkercrab": ["goal_src/import/lurkercrab-ag.gc"], + "muse": ["goal_src/import/muse-ag.gc"], + "puffer": ["goal_src/import/puffer-ag.gc"], + "robotboss-misc": [ + "goal_src/import/silodoor-ag.gc", + "goal_src/import/ecoclaw-ag.gc", + "goal_src/import/finalbosscam-ag.gc" + ], + "sage-finalboss": [ + "goal_src/import/plat-eco-finalboss-ag.gc", + "goal_src/import/green-sagecage-ag.gc", + "goal_src/import/jak-white-ag.gc", + "goal_src/import/robotboss-cinematic-ag.gc" + ], + "target-racer-h": ["goal_src/import/balloon-ag.gc"], + "gambler": ["goal_src/import/gambler-ag.gc"], + "lavatube-obs": [ + "goal_src/import/lavafallsewerb-ag.gc", + "goal_src/import/lavashortcut-ag.gc", + "goal_src/import/lavabase-ag.gc", + "goal_src/import/lavafallsewera-ag.gc", + "goal_src/import/chainmine-ag.gc", + "goal_src/import/lavafall-ag.gc", + "goal_src/import/lavaballoon-ag.gc", + "goal_src/import/darkecobarrel-ag.gc", + "goal_src/import/lavayellowtarp-ag.gc" + ], + "assistant-firecanyon": ["goal_src/import/assistant-firecanyon-ag.gc"], + "sharkey": ["goal_src/import/sharkey-ag.gc"], + "bonelurker": ["goal_src/import/bonelurker-ag.gc"], + "pelican": ["goal_src/import/pelican-ag.gc"], + "warrior": ["goal_src/import/warrior-ag.gc"], + "maincave-obs": [ + "goal_src/import/caveelevator-ag.gc", + "goal_src/import/cavespatulatwo-ag.gc", + "goal_src/import/cavetrapdoor-ag.gc", + "goal_src/import/maincavecam-ag.gc", + "goal_src/import/cavecrusher-ag.gc", + "goal_src/import/cavespatula-darkcave-ag.gc" + ], + "fisher": [ + "goal_src/import/fish-net-ag.gc", + "goal_src/import/fisher-ag.gc", + "goal_src/import/catch-fisha-ag.gc", + "goal_src/import/catch-fishb-ag.gc", + "goal_src/import/catch-fishc-ag.gc" + ], + "villagep-obs": [ + "goal_src/import/warp-gate-switch-ag.gc", + "goal_src/import/village-cam-ag.gc" + ], + "citb-bunny": ["goal_src/import/citb-bunny-ag.gc"], + "flying-lurker": [ + "goal_src/import/ogrecam-ag.gc", + "goal_src/import/plunger-lurker-ag.gc", + "goal_src/import/flying-lurker-ag.gc" + ], + "shover": ["goal_src/import/shover-ag.gc"], + "ice-cube": [ + "goal_src/import/ice-cube-ag.gc", + "goal_src/import/ice-cube-break-ag.gc" + ], + "baby-spider": ["goal_src/import/baby-spider-ag.gc"], + "minecart": ["goal_src/import/minecartsteel-ag.gc"], + "bird-lady": ["goal_src/import/bird-lady-ag.gc"], + "spider-egg": ["goal_src/import/spider-egg-ag.gc"], + "wall-plat": ["goal_src/import/wall-plat-ag.gc"], + "oracle": ["goal_src/import/oracle-ag.gc"], + "basebutton": ["goal_src/import/generic-button-ag.gc"], + "miners": [ + "goal_src/import/minershort-ag.gc", + "goal_src/import/cavegem-ag.gc", + "goal_src/import/minertall-ag.gc" + ], + "farmer": ["goal_src/import/farmer-ag.gc"], + "rolling-race-ring": ["goal_src/import/race-ring-ag.gc"], + "quicksandlurker": ["goal_src/import/quicksandlurker-ag.gc"], + "misty-warehouse": [ + "goal_src/import/silostep-ag.gc", + "goal_src/import/rounddoor-ag.gc" + ], + "yeti": ["goal_src/import/yeti-ag.gc"], + "hopper": ["goal_src/import/hopper-ag.gc"], + "target-death": ["goal_src/import/deathcam-ag.gc"], + "spiderwebs": ["goal_src/import/spiderwebs-ag.gc"], + "misty-teetertotter": ["goal_src/import/teetertotter-ag.gc"], + "village2-obs": [ + "goal_src/import/pontoonfive-ag.gc", + "goal_src/import/allpontoons-ag.gc", + "goal_src/import/medres-village2-ag.gc", + "goal_src/import/exit-chamber-dummy-ag.gc", + "goal_src/import/village2cam-ag.gc", + "goal_src/import/fireboulder-ag.gc", + "goal_src/import/ogreboss-village2-ag.gc", + "goal_src/import/ceilingflag-ag.gc", + "goal_src/import/medres-rolling1-ag.gc", + "goal_src/import/medres-rolling-ag.gc", + "goal_src/import/pontoonten-ag.gc" + ], + "plant-boss": ["goal_src/import/plant-boss-ag.gc"], + "snow-bunny": ["goal_src/import/snow-bunny-ag.gc"], + "sunken-elevator": ["goal_src/import/sunken-elevator-ag.gc"], + "sage": ["goal_src/import/sage-ag.gc"], + "robotboss-h": ["goal_src/import/robotboss-ag.gc"], + "geologist": ["goal_src/import/geologist-ag.gc"], + "village3-obs": [ + "goal_src/import/medres-ogre-ag.gc", + "goal_src/import/medres-finalboss-ag.gc", + "goal_src/import/pistons-ag.gc", + "goal_src/import/gondola-ag.gc", + "goal_src/import/medres-ogre2-ag.gc", + "goal_src/import/medres-ogre3-ag.gc", + "goal_src/import/gondolacables-ag.gc" + ], + "double-lurker": [ + "goal_src/import/double-lurker-ag.gc", + "goal_src/import/double-lurker-top-ag.gc" + ], + "ogreboss": ["goal_src/import/ogreboss-ag.gc"], + "swamp-rat": ["goal_src/import/swamp-rat-ag.gc"], + "sculptor": [ + "goal_src/import/sculptor-muse-ag.gc", + "goal_src/import/sculptor-ag.gc" + ], + "seagull": ["goal_src/import/seagull-ag.gc"], + "mayor": ["goal_src/import/mayor-ag.gc"], + "final-door": [ + "goal_src/import/power-left-ag.gc", + "goal_src/import/power-right-ag.gc", + "goal_src/import/powercellalt-ag.gc" + ], + "assistant-lavatube": ["goal_src/import/assistant-lavatube-start-ag.gc"], + "launcherdoor": [ + "goal_src/import/launcherdoor-maincave-ag.gc", + "goal_src/import/launcherdoor-ag.gc" + ], + "title-obs": [ + "goal_src/import/logo-cam-ag.gc", + "goal_src/import/logo-black-ag.gc", + "goal_src/import/logo-ag.gc", + "goal_src/import/logo-volumes-ag.gc", + "goal_src/import/ndi-cam-ag.gc", + "goal_src/import/ndi-ag.gc", + "goal_src/import/ndi-volumes-ag.gc" + ], + "whirlpool": ["goal_src/import/whirlpool-ag.gc"], + "lurkerworm": ["goal_src/import/lurkerworm-ag.gc"], + "collectables": [ + "goal_src/import/buzzer-ag.gc", + "goal_src/import/ecovalve-ag.gc", + "goal_src/import/money-ag.gc", + "goal_src/import/fuel-cell-ag.gc" + ], + "water-anim": [ + "goal_src/import/water-anim-maincave-ag.gc", + "goal_src/import/water-anim-village3-ag.gc", + "goal_src/import/water-anim-finalboss-ag.gc", + "goal_src/import/water-anim-maincave-water-ag.gc", + "goal_src/import/water-anim-sunken-ag.gc", + "goal_src/import/water-anim-lavatube-ag.gc", + "goal_src/import/water-anim-robocave-ag.gc", + "goal_src/import/water-anim-jungle-ag.gc", + "goal_src/import/water-anim-ogre-ag.gc", + "goal_src/import/water-anim-training-ag.gc", + "goal_src/import/water-anim-darkcave-ag.gc", + "goal_src/import/water-anim-village1-ag.gc", + "goal_src/import/water-anim-rolling-ag.gc", + "goal_src/import/water-anim-misty-ag.gc", + "goal_src/import/water-anim-sunken-dark-eco-ag.gc", + "goal_src/import/water-anim-village2-ag.gc" + ], + "ropebridge": [ + "goal_src/import/vil3-bridge-36-ag.gc", + "goal_src/import/ropebridge-36-ag.gc", + "goal_src/import/ropebridge-32-ag.gc", + "goal_src/import/ropebridge-52-ag.gc", + "goal_src/import/snow-bridge-36-ag.gc", + "goal_src/import/ropebridge-70-ag.gc" + ], + "robotboss": [ + "goal_src/import/robotboss-redeco-ag.gc", + "goal_src/import/robotboss-blueeco-ag.gc", + "goal_src/import/robotboss-yelloweco-ag.gc" + ], + "steam-cap": ["goal_src/import/steam-cap-ag.gc"], + "ogre-obs": [ + "goal_src/import/ogre-step-ag.gc", + "goal_src/import/tntbarrel-ag.gc", + "goal_src/import/ogre-bridge-ag.gc", + "goal_src/import/shortcut-boulder-ag.gc", + "goal_src/import/medres-snow-ag.gc", + "goal_src/import/ogre-bridgeend-ag.gc", + "goal_src/import/ogre-isle-ag.gc" + ], + "assistant-village3": ["goal_src/import/assistant-village3-ag.gc"], + "babak": ["goal_src/import/babak-ag.gc"], + "lavatube-energy": [ + "goal_src/import/energyball-ag.gc", + "goal_src/import/energybase-ag.gc", + "goal_src/import/energyhub-ag.gc", + "goal_src/import/energyarm-ag.gc", + "goal_src/import/energydoor-ag.gc" + ], + "mother-spider-egg": ["goal_src/import/spider-egg-ag.gc"], + "plat-flip": ["goal_src/import/plat-flip-ag.gc"], + "assistant-citadel": ["goal_src/import/assistant-lavatube-end-ag.gc"], + "bird-lady-beach": ["goal_src/import/bird-lady-beach-ag.gc"], + "orb-cache": ["goal_src/import/orb-cache-top-ag.gc"], + "robotboss-weapon": [ + "goal_src/import/darkecobomb-ag.gc", + "goal_src/import/greenshot-ag.gc", + "goal_src/import/redring-ag.gc" + ], + "citb-plat": [ + "goal_src/import/citb-exit-plat-ag.gc", + "goal_src/import/plat-eco-citb-ag.gc", + "goal_src/import/plat-citb-ag.gc", + "goal_src/import/citb-stopbox-ag.gc", + "goal_src/import/citb-firehose-ag.gc", + "goal_src/import/citb-rotatebox-ag.gc", + "goal_src/import/citb-chain-plat-ag.gc", + "goal_src/import/citb-donut-ag.gc" + ], + "gnawer": ["goal_src/import/gnawer-ag.gc"], + "jungleb-obs": [ + "goal_src/import/eggtop-ag.gc", + "goal_src/import/jng-iris-door-ag.gc" + ], + "swamp-rat-nest": ["goal_src/import/swamp-rat-nest-ag.gc"], + "wedge-plats": [ + "goal_src/import/wedge-plat-outer-ag.gc", + "goal_src/import/wedge-plat-ag.gc" + ], + "sunken-fish": ["goal_src/import/sunkenfisha-ag.gc"], + "sage-bluehut": ["goal_src/import/sage-bluehut-ag.gc"] } diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt index cb54228fc7..c2b4955b67 100644 --- a/game/CMakeLists.txt +++ b/game/CMakeLists.txt @@ -126,7 +126,9 @@ set(RUNTIME_SOURCE graphics/texture/TexturePool.cpp graphics/pipelines/opengl.cpp system/vm/dmac.cpp - system/vm/vm.cpp) + system/vm/vm.cpp + tools/subtitles/subtitle_editor.h + tools/subtitles/subtitle_editor.cpp) find_package(Git) diff --git a/game/assets/game_subtitle.gp b/game/assets/game_subtitle.gp index d262da393f..e1961a8f16 100644 --- a/game/assets/game_subtitle.gp +++ b/game/assets/game_subtitle.gp @@ -4,8 +4,8 @@ ;; you can find the game-text-version parsing in .cpp and an enum in goal-lib.gc (subtitle - (jak1-v1 "game/assets/jak1/subtitle/game_subtitle_en.gs") - (jak1-v1 "game/assets/jak1/subtitle/game_subtitle_es.gs") + (jak1-v1 "game/assets/jak1/subtitle/game_subtitle_en.gd") + (jak1-v1 "game/assets/jak1/subtitle/game_subtitle_es.gd") ) diff --git a/game/assets/jak1/subtitle/game_subtitle_en.gs b/game/assets/jak1/subtitle/game_subtitle_en.gd similarity index 68% rename from game/assets/jak1/subtitle/game_subtitle_en.gs rename to game/assets/jak1/subtitle/game_subtitle_en.gd index 4566d797e7..10aa5491b7 100644 --- a/game/assets/jak1/subtitle/game_subtitle_en.gs +++ b/game/assets/jak1/subtitle/game_subtitle_en.gd @@ -2,6 +2,7 @@ ;; ----------------- ;; intro +;; ----------------- ("sage-intro-sequence-a" (24 :offscreen "SAGE" "I HAVE SPENT MY LIFE SEARCHING FOR THE ANSWERS THAT MY FATHER, AND MY FATHER'S FATHERS FAILED TO FIND.") @@ -27,42 +28,6 @@ (2440 "DAXTER" "HEY! UH, JAK? OL' GREEN STUFF TOLD US NOT TO COME HERE!") ) -("sidekick-human-intro-sequence-b" - (547 "OLD MAN" "CONTINUE YOUR SEARCH FOR ARTIFACTS AND ECO.") - (674 "OLD MAN" "IF THE LOCALS POSSESS PRECURSOR ITEMS, YOU KNOW WHAT TO DO.") - (842 "WOMAN" "DEAL HARSHLY WITH ANYBODY WHO STRAYS FROM THE VILLAGE.") - (937 "WOMAN" "WE WILL ATTACK IT IN DUE TIME.") - (1027) - ) - -("sidekick-human-intro-sequence-c" - (12 "DAXTER" "WHAT ARE WE DOING HERE ANYWAY, JAK? THIS PLACE GIVES ME THE CREEPS!") - (151) - (369 "DAXTER" "HUH?") - (404 "DAXTER" "(GROAN) STUPID PRECURSOR JUNK!") - (496) - (577 "DAXTER" "EEK! WHAT IS THAT DARK OOZE? IT SURE DON'T LOOK FRIENDLY.") - (721) - (777 "DAXTER" "THE SAGE YAPS ON ABOUT THE PRECURSORS THAT BUILT THIS PLACE ALL THE TIME.") - (909 "DAXTER" "\"WHERE DID THEY GO? WHY DID THEY BUILD THIS CRAP?\"") - (1014 "DAXTER" "NOW I LIKE PRECURSOR ORBS AND POWER CELLS AS MUCH AS THE NEXT GUY") - (1124 "DAXTER" "BUT IF YOU ASK ME, THEY MUST HAVE BEEN REAL LOSERS.") - (1212 "DAXTER" "WHOA! HOW DID YOU DO THAT?") - (1271) - (1331 "DAXTER" "JAK, I THINK WE'RE IN TROUBLE!") - (1386) - (1857 "DAXTER" "MAN, THAT STUNG.") - (1913) - (1941 "DAXTER" "I TOLD YOU WE SHOULDN'T HAVE COME HERE, AND YOU LISTENED?") - (2028) - (2057 "DAXTER" "WHAAAT?") - (2101) - (2189 "DAXTER" "WAAAAAAAAHHHHHHHH!!") - (2307 "DAXTER" "OKAY, OKAY. I'M FINE, I'M FINE...") - (2431) - (2481 "DAXTER" "WAAAAAAAAHHHHHHHH!!") - ) - ("sage-intro-sequence-d1" (137 "SAGE" "WHAT IN GREEN TARNATION DO YOU TWO WANT?") (220 "DAXTER" "WE- WE- WE WAS- THEY WAS- I'M- I WAS-") @@ -137,166 +102,239 @@ (1439 :offscreen "SAGE" "GET OUT OF HERE!") ) +("sidekick-human-intro-sequence-b" + (547 "OLD MAN" "CONTINUE YOUR SEARCH FOR ARTIFACTS AND ECO.") + (674 "OLD MAN" "IF THE LOCALS POSSESS PRECURSOR ITEMS, YOU KNOW WHAT TO DO.") + (842 "WOMAN" "DEAL HARSHLY WITH ANYBODY WHO STRAYS FROM THE VILLAGE.") + (937 "WOMAN" "WE WILL ATTACK IT IN DUE TIME.") + (1027) + ) + +("sidekick-human-intro-sequence-c" + (12 "DAXTER" "WHAT ARE WE DOING HERE ANYWAY, JAK? THIS PLACE GIVES ME THE CREEPS!") + (151) + (369 "DAXTER" "HUH?") + (404 "DAXTER" "(GROAN) STUPID PRECURSOR JUNK!") + (496) + (577 "DAXTER" "EEK! WHAT IS THAT DARK OOZE? IT SURE DON'T LOOK FRIENDLY.") + (721) + (777 "DAXTER" "THE SAGE YAPS ON ABOUT THE PRECURSORS THAT BUILT THIS PLACE ALL THE TIME.") + (909 "DAXTER" "\"WHERE DID THEY GO? WHY DID THEY BUILD THIS CRAP?\"") + (1014 "DAXTER" "NOW I LIKE PRECURSOR ORBS AND POWER CELLS AS MUCH AS THE NEXT GUY") + (1124 "DAXTER" "BUT IF YOU ASK ME, THEY MUST HAVE BEEN REAL LOSERS.") + (1212 "DAXTER" "WHOA! HOW DID YOU DO THAT?") + (1271) + (1331 "DAXTER" "JAK, I THINK WE'RE IN TROUBLE!") + (1386) + (1857 "DAXTER" "MAN, THAT STUNG.") + (1913) + (1941 "DAXTER" "I TOLD YOU WE SHOULDN'T HAVE COME HERE, AND YOU LISTENED?") + (2028) + (2057 "DAXTER" "WHAAAT?") + (2101) + (2189 "DAXTER" "WAAAAAAAAHHHHHHHH!!") + (2307 "DAXTER" "OKAY, OKAY. I'M FINE, I'M FINE...") + (2431) + (2481 "DAXTER" "WAAAAAAAAHHHHHHHH!!") + ) ;; ----------------- ;; sidekick +;; ----------------- +("sksp0001" :hint #x280 + (0 "DAXTER" "HEY! THAT SWEET-LOOKING BABE KEIRA WAS RIGHT! THERE ARE TRANS-PADS OUT HERE.") + ) + +("sksp0002" :hint #x281 + (0 "DAXTER" "TRUST ME, THOSE DARK ECO BOXES ARE BAD NEWS!") + ) + +("sksp0003" :hint #x282 + (0 "DAXTER" "I BET IF WE FOUND ALL OF THOSE SICKLY CUTE SCOUT FLIES ON EACH LEVEL") + (215 "DAXTER" "AT LEAST ONE WILL HAVE A POWER CELL!") + ) + +("sksp0004" :hint #x283 + (0 "DAXTER" "ALL RIGHT, TREASURE!") + ) + +("sksp0005" :hint #x284 + (0 "DAXTER" "THESE WOOD AND METAL BOXES DON'T LOOK THAT TOUGH!") + (166 "DAXTER" "I'LL BET THEY'LL BREAK IF YOU JUMP DIVE ONTO THEM.") + ) + +("sksp0006" :hint #x285 + (0 "DAXTER" "THOSE ALL-METAL BOXES ARE THICKER THAN A YAKOW SKULL!") + (188 "DAXTER" "BUT THERE MUST BE SOMETHING THAT CAN BUST 'EM OPEN.") + ) + +("sksp0007" :hint #x286 + (0 "DAXTER" "TWO WORDS: POWER CELLS. WE NEED TO FIND 'EM AND WE AREN'T LOOKIN' SO GOOD RIGHT NOW.") + ) + +("sksp0008" :hint #x287 + (0 "DAXTER" "WE NEED ORBS. ORBS, JAK! HOW ARE EVER GONNA BUY POWER CELLS IF WE DON'T COLLECT ORBS!") + ) + +("sksp0009" :hint #x251 + (0 "DAXTER" "WHAT A WEIRD-LOOKING THING! I'LL BET WE CAN GET THIS OPEN") + (206 "DAXTER" "IF YOU'RE POWERED UP WITH THAT ZAPPY BLUE ECO STUFF.") + ) ("sksp0014" :hint #x233 (0 "DAXTER" "WE SCROUNGED ENOUGH ORBS TO TRADE FOR A POWER CELL.") (215 "DAXTER" "LET'S GET BACK AND MAKE THE SWITCH ALREADY!") ) -("sksp0009" :hint #x251 - (0 "DAXTER" "WHAT A WEIRD-LOOKING THING! I'LL BET WE CAN GET THIS OPEN") - (206 "DAXTER" "IF YOU'RE POWERED UP WITH THAT ZAPPY BLUE ECO STUFF.") - ) + ("sksp0035" :hint #x25c (0 "DAXTER" "YEAH, SO WE GOT A PRECURSOR LAUNCHER HERE") (117 "DAXTER" "BUT YOU AREN'T POWERED UP WITH THE BLUE STUFF. SO IT AIN'T GONNA HELP US!") ) -("sksp0001" :hint #x280 (0 "DAXTER" "HEY! THAT SWEET-LOOKING BABE KEIRA WAS RIGHT! THERE ARE TRANS-PADS OUT HERE.")) -("sksp0002" :hint #x281 (0 "DAXTER" "TRUST ME, THOSE DARK ECO BOXES ARE BAD NEWS!")) -("sksp0003" :hint #x282 - (0 "DAXTER" "I BET IF WE FOUND ALL OF THOSE SICKLY CUTE SCOUT FLIES ON EACH LEVEL") - (215 "DAXTER" "AT LEAST ONE WILL HAVE A POWER CELL!") - ) -("sksp0004" :hint #x283 (0 "DAXTER" "ALL RIGHT, TREASURE!")) -("sksp0005" :hint #x284 - (0 "DAXTER" "THESE WOOD AND METAL BOXES DON'T LOOK THAT TOUGH!") - (166 "DAXTER" "I'LL BET THEY'LL BREAK IF YOU JUMP DIVE ONTO THEM.") - ) -("sksp0006" :hint #x285 - (0 "DAXTER" "THOSE ALL-METAL BOXES ARE THICKER THAN A YAKOW SKULL!") - (188 "DAXTER" "BUT THERE MUST BE SOMETHING THAT CAN BUST 'EM OPEN.") - ) -("sksp0007" :hint #x286 (0 "DAXTER" "TWO WORDS: POWER CELLS. WE NEED TO FIND 'EM AND WE AREN'T LOOKIN' SO GOOD RIGHT NOW.")) -("sksp0008" :hint #x287 (0 "DAXTER" "WE NEED ORBS. ORBS, JAK! HOW ARE EVER GONNA BUY POWER CELLS IF WE DON'T COLLECT ORBS!")) -("sksp009a" :hint 0 (0 "DAXTER" "HA HA! WE SHOWED HIM A THING OR TWO.")) -("sksp009b" :hint #x28e (0 "DAXTER" "ALL RIGHT, MORE ORBS!")) -("sksp009c" :hint #x28f (0 "DAXTER" "DO ME A FAVOR AND KEEP AWAY FROM THOSE DARK ECO BOXES!")) -("sksp009d" :hint #x290 (0 "DAXTER" "YEAH-HA-HA-HA!")) -("sksp009e" :hint #x291 (0 "DAXTER" "WA-HOO!")) -("sksp009f" :hint #x292 (0 "DAXTER" "GET SOME, GET SOME! HA HA HA!")) -("sksp009g" :hint #x293 (0 "DAXTER" "RIGHT BEHIND YA, JAK!")) -("sksp009i" :hint #x294 (0 "DAXTER" "DID WE FIND ALL THE SCOUT FLIES IN THIS AREA?")) -("sksp009j" :hint #x295 (0 "DAXTER" "HEY! IT LOOKS LIKE SCOUT FLIES ARE ALWAYS IN RED BOXES.")) -("sksp009k" :hint #x296 (0 "DAXTER" "ALRIGHT! YOU FOUND ALL THE SCOUT FLIES IN THIS AREA!")) -("sksp0071" :hint #x2a5 (0 "DAXTER" "GET THE RED ECO!")) -("sksp0072" :hint #x2a6 (0 "DAXTER" "RED ECO MAKES YOUR ATTACKS STRONGER.")) -("sksp0073" :hint #x2a7 (0 "DAXTER" "WE NEED BLUE ECO TO CHARGE THIS PLATFORM UP!")) -("sksp0145" :hint #x359 (0 "DAXTER" "WOW! YOU CAN SHOOT FIRE BALLS WHEN YOU'RE POWERED UP WITH YELLOW ECO.")) +("sksp0071" :hint #x2a5 + (0 "DAXTER" "GET THE RED ECO!") + ) +("sksp0072" :hint #x2a6 + (0 "DAXTER" "RED ECO MAKES YOUR ATTACKS STRONGER.") + ) +("sksp0073" :hint #x2a7 + (0 "DAXTER" "WE NEED BLUE ECO TO CHARGE THIS PLATFORM UP!") + ) + +("sksp009a" :hint #x0 + (0 "DAXTER" "HA HA! WE SHOWED HIM A THING OR TWO.") + ) + +("sksp009b" :hint #x28e + (0 "DAXTER" "ALL RIGHT, MORE ORBS!") + ) + +("sksp009c" :hint #x28f + (0 "DAXTER" "DO ME A FAVOR AND KEEP AWAY FROM THOSE DARK ECO BOXES!") + ) + +("sksp009d" :hint #x290 + (0 "DAXTER" "YEAH-HA-HA-HA!") + ) + +("sksp009e" :hint #x291 + (0 "DAXTER" "WA-HOO!") + ) + +("sksp009f" :hint #x292 + (0 "DAXTER" "GET SOME, GET SOME! HA HA HA!") + ) + +("sksp009g" :hint #x293 + (0 "DAXTER" "RIGHT BEHIND YA, JAK!") + ) + +("sksp009i" :hint #x294 + (0 "DAXTER" "DID WE FIND ALL THE SCOUT FLIES IN THIS AREA?") + ) + +("sksp009j" :hint #x295 + (0 "DAXTER" "HEY! IT LOOKS LIKE SCOUT FLIES ARE ALWAYS IN RED BOXES.") + ) + +("sksp009k" :hint #x296 + (0 "DAXTER" "ALRIGHT! YOU FOUND ALL THE SCOUT FLIES IN THIS AREA!") + ) + +("sksp0145" :hint #x359 + (0 "DAXTER" "WOW! YOU CAN SHOOT FIRE BALLS WHEN YOU'RE POWERED UP WITH YELLOW ECO.") + ) ;; ----------------- ;; village1 +;; ----------------- +("ASSTLP01" :hint #x0 + (0 "KEIRA" "HMM...") + ) -("SAGELP03" :hint 0 (0 "SAGE" "THESE TWO COULDN'T UNBLOCK THEIR EARS...")) -("SAGELP04" :hint 0 +("ASSTLP02" :hint #x0 + (0 "KEIRA" "GOTTA GET THIS HEAT SHIELD WORKING...") + ) + +("ASSTLP03" :hint #x0 + (0 "KEIRA" "WE NEED POWER CELLS TO FUEL THE HEAT SHIELD...") + ) + +("ASSTLP04" :hint #x0 + (0 "KEIRA" "WHERE'S MY SPANNER?") + ) + +("ASSTLP05" :hint #x0 + (0 "KEIRA" "FIRE CANYON IS SO HOT...") + ) + +("EXP-AM01" :hint #x0 + (0 "JAK'S UNCLE" "I NEED TO SET OFF ON MY JOURNEY SOON. I NEED PRECURSOR ORBS!") + ) + +("EXP-AM02" :hint #x0 + (0 "JAK'S UNCLE" "LET'S SEE, NOW WHERE SHALL I GO FIRST? EH...") + ) + +("EXP-AM03" :hint #x0 + (0 "JAK'S UNCLE" "OH I DO MISS THE OPEN ROAD.") + ) + +("EXP-AM04" :hint #x0 + (0 "JAK'S UNCLE" "SO MANY PLACES TO SEE, AND SO LITTLE TIME.") + ) + +("EXP-AM05" :hint #x0 + (0 "JAK'S UNCLE" "WELL IN MY DAY, YOU COULD WALK THE LANDS WITHOUT BEING ASSAULTED BY MONSTERS!") + ) + +("EXP-LO02" :hint #x0 + (0 "JAK'S UNCLE" "I NEED MORE PRECURSOR ORBS.") + ) + +("FAR-AM01" :hint #x0 + (0 "FARMER" "(SNORES) HERE, BESSIE... HERE, BESSIE BESSIE... THAT'S A GOOD GIRL...") + ) + +("FAR-AM02" :hint #x0 + (0 "FARMER" "GOTTA BRING IN THE CROPS 'FORE THE JUNE BUGS...") + ) + +("FAR-AM2A" :hint #x0 + (0 "FARMER" "GOTTA BRING IN THE CROPS 'FORE THE JUNE BUGS GET...") + ) + +("FAR-LO01" :hint #x0 + (0 "FARMER" "(SNORES)") + ) + +("FAR-LO1A" :hint #x0 + (0 "FARMER" "(SNORES)") + ) + +("SAGELP03" :hint #x0 + (0 "SAGE" "THESE TWO COULDN'T UNBLOCK THEIR EARS...") + ) + +("SAGELP04" :hint #x0 (0 "SAGE" "HE'LL JUST NEVER BE LIKE HIS UNCLE...") (221 "SAGE" "I DON'T CARE WHAT THE OTHERS SAY.") ) -("SAGELP05" :hint 0 (0 "SAGE" "HELLO, BLUE SAGE? HELLO? WHERE IN THE BLUE BLAZES COULD HE BE...")) -("SAGELP06" :hint 0 (0 "SAGE" "MY, MY, MY...")) -("SAGELP11" :hint 0 (0 "SAGE" "ANYONE? ANYONE AT ALL? COME IN?")) -("ASSTLP01" :hint 0 (0 "KEIRA" "HMM...")) -("ASSTLP02" :hint 0 (0 "KEIRA" "GOTTA GET THIS HEAT SHIELD WORKING...")) -("ASSTLP03" :hint 0 (0 "KEIRA" "WE NEED POWER CELLS TO FUEL THE HEAT SHIELD...")) -("ASSTLP04" :hint 0 (0 "KEIRA" "WHERE'S MY SPANNER?")) -("ASSTLP05" :hint 0 (0 "KEIRA" "FIRE CANYON IS SO HOT...")) -("EXP-AM01" :hint 0 (0 "JAK'S UNCLE" "I NEED TO SET OFF ON MY JOURNEY SOON. I NEED PRECURSOR ORBS!")) -("EXP-AM02" :hint 0 (0 "JAK'S UNCLE" "LET'S SEE, NOW WHERE SHALL I GO FIRST? EH...")) -("EXP-AM03" :hint 0 (0 "JAK'S UNCLE" "OH I DO MISS THE OPEN ROAD.")) -("EXP-AM04" :hint 0 (0 "JAK'S UNCLE" "SO MANY PLACES TO SEE, AND SO LITTLE TIME.")) -("EXP-AM05" :hint 0 (0 "JAK'S UNCLE" "WELL IN MY DAY, YOU COULD WALK THE LANDS WITHOUT BEING ASSAULTED BY MONSTERS!")) -("EXP-LO02" :hint 0 (0 "JAK'S UNCLE" "I NEED MORE PRECURSOR ORBS.")) -("FAR-AM01" :hint 0 (0 "FARMER" "(SNORES) HERE, BESSIE... HERE, BESSIE BESSIE... THAT'S A GOOD GIRL...")) -("FAR-AM02" :hint 0 (0 "FARMER" "GOTTA BRING IN THE CROPS 'FORE THE JUNE BUGS...")) -("FAR-AM2A" :hint 0 (0 "FARMER" "GOTTA BRING IN THE CROPS 'FORE THE JUNE BUGS GET...")) -("FAR-LO01" :hint 0 (0 "FARMER" "(SNORES)")) -("FAR-LO1A" :hint 0 (0 "FARMER" "(SNORES)")) -("sksp0010" :hint #x231 (0 "DAXTER" "HEY! LET'S GO CHECK OUT OL' FISH BREATH'S SPEED-BOAT AT THE DOCK!")) -("sksp0013" :hint #x232 - (0 "DAXTER" "DID YOU SEE THE SIZE OF THE BITE THAT LURKER SHARK TOOK OUT OF THE FISHERMAN'S BOAT?") - (315) - (337 "DAXTER" "WE BEST STAY WAY CLEAR OF THEM! I DON'T THINK WE CAN TACKLE A CREATURE THAT DAG NASTY.") - ) -("sksp0015" :hint #x234 - (0 "DAXTER" "THIS MUST BE A PRECURSOR ORACLE LIKE THE SAGE ALWAYS GOES ON ABOUT.") - (228 "DAXTER" "I HOPE THEY WEREN'T AS UGLY IN PERSON!") - ) -("sksp0017" :hint #x235 (0 "DAXTER" "WHOA! CHECK OUT THAT FUNKY SCULPTURE SITTIN' ON THE ROCKS OVER THERE!")) -("sksp0043" :hint #x24e - (0 "DAXTER" "MAYBE WE SHOULD GO ROOT OUT THE FISHERMAN.") - (106 "DAXTER" "I HEARD HE WAS IN THE JUNGLE FISHING BY THE LOWER RIVER!") - ) -("sksp018a" :hint #x297 - (0 "DAXTER" "THAT LAZY FARMER OWES US A POWER CELL!") - (156 "DAXTER" "LET'S GO TALK TO HIM!") - ) -("asstvb04" :hint #x2b1 - (0 "KEIRA" "GOOD, YOU'VE COLLECTED ENOUGH POWER CELLS TO FUEL MY HEAT SHIELD!") - (233 "KEIRA" "MEET ME BY THE ENTRANCE TO THE FIRE CANYON BY CLIMBING THE CLIFF BEHIND THE FARMER'S HOUSE.") - (559 "KEIRA" "BRING THE POWER CELLS, AND HURRY! MY FATHER SAYS HE'S SEEN MORE LURKERS AROUND!") - ) -("asstvb08" :hint #x2b2 - (0 "KEIRA" "HURRY UP WITH THOSE POWER CELLS. I'M WAITING AT THE HEAD OF FIRE CANYON") - (260 "KEIRA" "AT THE TOP OF THE CLIFF BEHIND THE FARMER'S HOUSE!") +("SAGELP05" :hint #x0 + (0 "SAGE" "HELLO, BLUE SAGE? HELLO? WHERE IN THE BLUE BLAZES COULD HE BE...") ) -("sage-introduction-misty-cannon" - (24 "SAGE" "OH, IT'S THE CONQUERING HEROES.") - (136 "SAGE" "GOOD, I WANTED TO TALK WITH YOU TWO ABOUT SOMETHING SERIOUS.") - (258) - (261 :offscreen "SAGE" "THERE APPEARS TO BE QUITE A BIT OF LURKER ACTIVITY ON MISTY ISLAND.") - (387) - (393 :offscreen "SAGE" "I CAN SEE THEM BOMBARDING THE PRECURSOR SILO FROM MY LOOKOUT TOWER.") - (525) - (528 "SAGE" "IF THE LURKERS OPEN IT UP AND RELEASE THE DARK ECO, WE COULD ALL END UP") - (663 "SAGE" "RUNNING AROUND LOOKING AS RIDICULOUS AS THIS ANNOYING LITTLE SPECIMEN.") - (788) - (807 "SAGE" "JAK, IT'S TIME FOR YOU TO PROVE YOUR WORTH.") - (906 "SAGE" "GET THE FISHERMAN TO LET YOU TAKE HIS BOAT BACK TO MISTY ISLAND") - (1018 "SAGE" "GET TO THE TOP OF THE PRECURSOR SILO AND TAKE OUT THAT CANNON.") - (1171) - (1177 "DAXTER" "AND... WHAT ABOUT ME?") - (1232) - (1235 "SAGE" "YOU? WHY DON'T YOU MOP MY FLOORS? THEY SEEM TO HAVE LOST THEIR SHINE LATELY.") +("SAGELP06" :hint #x0 + (0 "SAGE" "MY, MY, MY...") ) -("sage-reminder-1-misty-cannon" - (24 "SAGE" "THE BOMBARDMENT IS GETTING WORSE!") - (86 :offscreen "SAGE" "FIND THE FISHERMAN IN THE JUNGLE, GET PERMISSION TO USE HIS BOAT") - (215 :offscreen "SAGE" "AND GET OVER TO MISTY ISLAND TO STOP THE CANNON.") - (317) - (320 "SAGE" "AND YOU DAXTER, YOU NEED TO GET MOPPING. THIS PLACE IS A MESS!") - ) - -("sage-reminder-1-ecorocks" - (28 "SAGE" "SOME \"BRAVE ADVENTURERS\" YOU TWO ARE.") - (132 "SAGE" "BACK ALREADY AND WITHOUT CLEARING MY BLOCKED ECO HARVESTERS!") - (244) - (252 :offscreen "SAGE" "THEY'RE ON THE FAR SIDE OF THE BEACH, BOYS.") - (365) - (366 :offscreen "SAGE" "NOW...") - (412 :offscreen "SAGE" "GET MOVING!") - ) - -("sage-reminder-1-generic" - (54 "SAGE" "WHAT ARE YOU TWO DOING HERE? YOU HAVE POWER CELLS TO COLLECT!") - (178) - (181 "SAGE" "LEAVE ME ALONE... UNTIL YOU HAVE THEM!") - ) - -("sage-reminder-2-generic" - (27 "SAGE" "YOU TWO JUST DON'T UNDERSTAND THE GRAVITY OF THE SITUATION.") - (137 "SAGE" "THIS IS ABOUT MORE THAN A SINGLE BOY GONE FUZZY. THIS IS ABOUT LIFE AS WE KNOW IT.") - (320) - (323 "SAGE" "NOW, GET OUT THERE AND FULFILL YOUR DESTINY.") - (455) +("SAGELP11" :hint #x0 + (0 "SAGE" "ANYONE? ANYONE AT ALL? COME IN?") ) ("assistant-introduction-blue-eco-switch" @@ -318,12 +356,6 @@ (1246) ) -("assistant-reminder-1-blue-eco-switch" - (23 "KEIRA" "I'M SURE IF YOU LOOKED INSIDE THE PRECURSOR FORBIDDEN TEMPLE") - (143 "KEIRA" "YOU'LL FIND A WAY TO TURN ON THAT CAPPED BLUE ECO VENT ON SENTINEL BEACH.") - (295) - ) - ("assistant-introduction-race-bike" (9 "DAXTER" "HEY BABY, WHAT'CHA WORKIN' ON?") (63 "KEIRA" "BE CAREFUL WITH THAT! I'M TRYING TO FIGURE HOW TO TRANSPORT THE ZOOMER") @@ -335,37 +367,31 @@ (665) ) -("assistant-reminder-1-race-bike" - (19 "KEIRA" "I'VE DEFINITELY FIGURED OUT HOW TO USE THE TRANS-PADS.") - (106 "KEIRA" "NOW GO TO MISTY ISLAND AND I'LL SEND THE ZOOMER OVER TO YOU.") - (229) +("assistant-reminder-1-blue-eco-switch" + (23 "KEIRA" "I'M SURE IF YOU LOOKED INSIDE THE PRECURSOR FORBIDDEN TEMPLE") + (143 "KEIRA" "YOU'LL FIND A WAY TO TURN ON THAT CAPPED BLUE ECO VENT ON SENTINEL BEACH.") + (295) ) ("assistant-reminder-1-generic" (25 "KEIRA" "HEY GUYS! KEEP COLLECTING POWER CELLS, THEY'RE THE KEY TO CONTINUING OUR JOURNEY NORTH.") ) -("farmer-introduction" - (1 "FARMER" "GOTTA MILK THOSE YAKOWS, GOTTA MILK THOSE YAKOWS...") - (113 "FARMER" "OH! IT'S YOU... JUST RESTIN' MY TRIED BONES. I'VE BEEN TRYING TO GET THOSE ORNERY YAKOWS") - (317 "FARMER" "BACK INTO THE PEN ALL DAY! SOME STRANGE CREATURES TRIED TO STEAL 'EM EARLIER.") - (454) - (456 "FARMER" "YOU THINK YOU CAN HELP AN OLD MAN TRY TO GET 'EM BACK INTO THE CORRAL?") +("assistant-reminder-1-race-bike" + (19 "KEIRA" "I'VE DEFINITELY FIGURED OUT HOW TO USE THE TRANS-PADS.") + (106 "KEIRA" "NOW GO TO MISTY ISLAND AND I'LL SEND THE ZOOMER OVER TO YOU.") + (229) ) -("farmer-reminder-1" - (0 "FARMER" "HEY! MY YAKOWS ARE STILL ON THE LOOSE! EH, COULD YOU BRING 'EM BACK FOR ME ALREADY?") +("asstvb04" :hint #x2b1 + (0 "KEIRA" "GOOD, YOU'VE COLLECTED ENOUGH POWER CELLS TO FUEL MY HEAT SHIELD!") + (233 "KEIRA" "MEET ME BY THE ENTRANCE TO THE FIRE CANYON BY CLIMBING THE CLIFF BEHIND THE FARMER'S HOUSE.") + (559 "KEIRA" "BRING THE POWER CELLS, AND HURRY! MY FATHER SAYS HE'S SEEN MORE LURKERS AROUND!") ) -("farmer-reminder-2" - (0 "FARMER" "THOSE DARN YAKOWS ARE STILL ROAMIN' FREE.") - (85 "FARMER" "AIN'T YOU GONNA CHASE 'EM INTO THE CORRAL ALREADY?") - ) - -("farmer-resolution" - (0 "FARMER" "AH, WELL DONE MY BOY. YOU ACTUALLY GOT THOSE FLEABAGS PACK INTO THE PEN.") - (134 "FARMER" "NOW I CAN SLEEP IN PEACE. TAKE THIS POWER CELL FOR YOUR TROUBLE.") - (340) +("asstvb08" :hint #x2b2 + (0 "KEIRA" "HURRY UP WITH THOSE POWER CELLS. I'M WAITING AT THE HEAD OF FIRE CANYON") + (260 "KEIRA" "AT THE TOP OF THE CLIFF BEHIND THE FARMER'S HOUSE!") ) ("explorer-introduction" @@ -400,9 +426,113 @@ (324 "JAK'S UNCLE" "I HOPE YOU PUT THIS HARD-EARNED POWER CELL TO GOOD USE. CHEERIO, TA TA, BYE BYE!") ) +("farmer-introduction" + (1 "FARMER" "GOTTA MILK THOSE YAKOWS, GOTTA MILK THOSE YAKOWS...") + (113 "FARMER" "OH! IT'S YOU... JUST RESTIN' MY TRIED BONES. I'VE BEEN TRYING TO GET THOSE ORNERY YAKOWS") + (317 "FARMER" "BACK INTO THE PEN ALL DAY! SOME STRANGE CREATURES TRIED TO STEAL 'EM EARLIER.") + (454) + (456 "FARMER" "YOU THINK YOU CAN HELP AN OLD MAN TRY TO GET 'EM BACK INTO THE CORRAL?") + ) + +("farmer-reminder-1" + (0 "FARMER" "HEY! MY YAKOWS ARE STILL ON THE LOOSE! EH, COULD YOU BRING 'EM BACK FOR ME ALREADY?") + ) + +("farmer-reminder-2" + (0 "FARMER" "THOSE DARN YAKOWS ARE STILL ROAMIN' FREE.") + (85 "FARMER" "AIN'T YOU GONNA CHASE 'EM INTO THE CORRAL ALREADY?") + ) + +("farmer-resolution" + (0 "FARMER" "AH, WELL DONE MY BOY. YOU ACTUALLY GOT THOSE FLEABAGS PACK INTO THE PEN.") + (134 "FARMER" "NOW I CAN SLEEP IN PEACE. TAKE THIS POWER CELL FOR YOUR TROUBLE.") + (340) + ) + +("sage-introduction-misty-cannon" + (24 "SAGE" "OH, IT'S THE CONQUERING HEROES.") + (136 "SAGE" "GOOD, I WANTED TO TALK WITH YOU TWO ABOUT SOMETHING SERIOUS.") + (258) + (261 :offscreen "SAGE" "THERE APPEARS TO BE QUITE A BIT OF LURKER ACTIVITY ON MISTY ISLAND.") + (387) + (393 :offscreen "SAGE" "I CAN SEE THEM BOMBARDING THE PRECURSOR SILO FROM MY LOOKOUT TOWER.") + (525) + (528 "SAGE" "IF THE LURKERS OPEN IT UP AND RELEASE THE DARK ECO, WE COULD ALL END UP") + (663 "SAGE" "RUNNING AROUND LOOKING AS RIDICULOUS AS THIS ANNOYING LITTLE SPECIMEN.") + (788) + (807 "SAGE" "JAK, IT'S TIME FOR YOU TO PROVE YOUR WORTH.") + (906 "SAGE" "GET THE FISHERMAN TO LET YOU TAKE HIS BOAT BACK TO MISTY ISLAND") + (1018 "SAGE" "GET TO THE TOP OF THE PRECURSOR SILO AND TAKE OUT THAT CANNON.") + (1171) + (1177 "DAXTER" "AND... WHAT ABOUT ME?") + (1232) + (1235 "SAGE" "YOU? WHY DON'T YOU MOP MY FLOORS? THEY SEEM TO HAVE LOST THEIR SHINE LATELY.") + ) + +("sage-reminder-1-ecorocks" + (28 "SAGE" "SOME \"BRAVE ADVENTURERS\" YOU TWO ARE.") + (132 "SAGE" "BACK ALREADY AND WITHOUT CLEARING MY BLOCKED ECO HARVESTERS!") + (244) + (252 :offscreen "SAGE" "THEY'RE ON THE FAR SIDE OF THE BEACH, BOYS.") + (365) + (366 :offscreen "SAGE" "NOW...") + (412 :offscreen "SAGE" "GET MOVING!") + ) + +("sage-reminder-1-generic" + (54 "SAGE" "WHAT ARE YOU TWO DOING HERE? YOU HAVE POWER CELLS TO COLLECT!") + (178) + (181 "SAGE" "LEAVE ME ALONE... UNTIL YOU HAVE THEM!") + ) + +("sage-reminder-1-misty-cannon" + (24 "SAGE" "THE BOMBARDMENT IS GETTING WORSE!") + (86 :offscreen "SAGE" "FIND THE FISHERMAN IN THE JUNGLE, GET PERMISSION TO USE HIS BOAT") + (215 :offscreen "SAGE" "AND GET OVER TO MISTY ISLAND TO STOP THE CANNON.") + (317) + (320 "SAGE" "AND YOU DAXTER, YOU NEED TO GET MOPPING. THIS PLACE IS A MESS!") + ) + +("sage-reminder-2-generic" + (27 "SAGE" "YOU TWO JUST DON'T UNDERSTAND THE GRAVITY OF THE SITUATION.") + (137 "SAGE" "THIS IS ABOUT MORE THAN A SINGLE BOY GONE FUZZY. THIS IS ABOUT LIFE AS WE KNOW IT.") + (320) + (323 "SAGE" "NOW, GET OUT THERE AND FULFILL YOUR DESTINY.") + (455) + ) + +("sksp0010" :hint #x231 + (0 "DAXTER" "HEY! LET'S GO CHECK OUT OL' FISH BREATH'S SPEED-BOAT AT THE DOCK!") + ) + +("sksp0013" :hint #x232 + (0 "DAXTER" "DID YOU SEE THE SIZE OF THE BITE THAT LURKER SHARK TOOK OUT OF THE FISHERMAN'S BOAT?") + (315) + (337 "DAXTER" "WE BEST STAY WAY CLEAR OF THEM! I DON'T THINK WE CAN TACKLE A CREATURE THAT DAG NASTY.") + ) + +("sksp0015" :hint #x234 + (0 "DAXTER" "THIS MUST BE A PRECURSOR ORACLE LIKE THE SAGE ALWAYS GOES ON ABOUT.") + (228 "DAXTER" "I HOPE THEY WEREN'T AS UGLY IN PERSON!") + ) + +("sksp0017" :hint #x235 + (0 "DAXTER" "WHOA! CHECK OUT THAT FUNKY SCULPTURE SITTIN' ON THE ROCKS OVER THERE!") + ) + +("sksp0043" :hint #x24e + (0 "DAXTER" "MAYBE WE SHOULD GO ROOT OUT THE FISHERMAN.") + (106 "DAXTER" "I HEARD HE WAS IN THE JUNGLE FISHING BY THE LOWER RIVER!") + ) + +("sksp018a" :hint #x297 + (0 "DAXTER" "THAT LAZY FARMER OWES US A POWER CELL!") + (156 "DAXTER" "LET'S GO TALK TO HIM!") + ) ;; ----------------- ;; oracle +;; ----------------- ("oracle-intro-1" (0 "ORACLE" "WHO AWAKENS THE ORACLE?") @@ -413,89 +543,225 @@ (390) (395 "ORACLE" "PRESENT TO ME 120 PRECURSOR ORBS FOR EACH POWER CELL I CONTAIN.") ) + +("oracle-left-eye-1" + (0 "ORACLE" "YOU HAVE PROVEN YOURSELF WORTHY. HERE IS A POWER CELL.") + ) + +("oracle-left-eye-2" + (0 "ORACLE" "FOR YOUR SACRIFICE, I OFFER YOU A POWER CELL.") + ) + +("oracle-left-eye-3" + (0 "ORACLE" "FOR YOUR EFFORT, A POWER CELL IS THE REWARD.") + ) + ("oracle-reminder-1" (0 "ORACLE" "BRING TO ME 120 PRECURSOR ORBS AND I WILL AWARD YOU A POWER CELL.") ) -("oracle-right-eye-1" (0 "ORACLE" "FOR YOUR GIFT, ANOTHER POWER CELL IS YOURS.")) -("oracle-left-eye-1" (0 "ORACLE" "YOU HAVE PROVEN YOURSELF WORTHY. HERE IS A POWER CELL.")) -("oracle-right-eye-2" (0 "ORACLE" "HERE IS ANOTHER POWER CELL FOR YOUR QUEST.")) -("oracle-left-eye-2" (0 "ORACLE" "FOR YOUR SACRIFICE, I OFFER YOU A POWER CELL.")) -("oracle-right-eye-3" (0 "ORACLE" "YOU HAVE OBTAINED ANOTHER POWER CELL.")) -("oracle-left-eye-3" (0 "ORACLE" "FOR YOUR EFFORT, A POWER CELL IS THE REWARD.")) +("oracle-right-eye-1" + (0 "ORACLE" "FOR YOUR GIFT, ANOTHER POWER CELL IS YOURS.") + ) +("oracle-right-eye-2" + (0 "ORACLE" "HERE IS ANOTHER POWER CELL FOR YOUR QUEST.") + ) + +("oracle-right-eye-3" + (0 "ORACLE" "YOU HAVE OBTAINED ANOTHER POWER CELL.") + ) ;; ----------------- ;; beach +;; ----------------- -("CHI-LO01" :hint 0 (0 "MAYOR" "...WINDMILL, MONSTERS, POWER, RE-ELECTION...")) -("CHI-LO02" :hint 0 (0 "MAYOR" "...THE POLLS, I... MONSTERS, HM... RE-ELECTION, OH...")) -("CHI-AM01" :hint 0 (0 "MAYOR" "THIS IS A CATASTROPHE. A CATASTROPHE I SAY!")) -("CHI-AM02" :hint 0 (0 "MAYOR" "THEY WANT ME TO GO INTO THE JUNGLE - ME! HO HO OH...") - (214) - (219 "MAYOR" "I'D SOONER WRESTLE AN ENRAGED FLUT FLUT.")) -("CHI-AM03" :hint 0 (0 "MAYOR" "...FIRST THE FISHERMAN'S BOAT, (STUTTERS) IT'S ATTACKED BY A MONSTER...") - (352) - (395 "MAYOR" "AND NOW, NOW THIS... WHAT ELSE COULD GO WRONG?")) -("CHI-AM04" :hint 0 (0 "MAYOR" "I DON'T... I'D, I'D... OOH, MAYBE I SHOULD RAISE TAXES (STUTTERS) TO PAY FOR THIS MESS.")) -("CHI-AM05" :hint 0 (0 "MAYOR" "PROBLEMS... PROBLEMS... PROBLEMS!")) -("CHI-AM06" :hint 0 (0 "MAYOR" "(SOBS) I'LL NEVER GET RE-ELECTED NOW...")) -("CHI-AM07" :hint 0 (0 "MAYOR" "(MOANS) WHAT'S HAPPENED TO THE VILLAGE'S ENERGY BEAM?")) -("CHI-AM08" :hint 0 (0 "MAYOR" "I... THEY, I DON'T... (MUTTERS)")) -("SCU-AM01" :hint 0 (0 "SCULPTOR" "AW... I JUST CAN'T DO IT ANYMORE.")) -("SCU-AM02" :hint 0 (0 "SCULPTOR" "AW, I WISH I HAD SOME INSPIRATION.")) -("SCU-AM03" :hint 0 (0 "SCULPTOR" "I'M AS INSPIRED AS... THIS ROCK.")) -("SCU-AM04" :hint 0 (0 "SCULPTOR" "AW... WHERE IS MY MUSE?")) -("SCU-AM05" :hint 0 (0 "SCULPTOR" "I CAN'T WORK LIKE THIS!")) -("SCU-AM06" :hint 0 (0 "SCULPTOR" "AW MAN... WILL SHE EVER COME BACK TO ME?")) -("SCU-LO01" :hint 0 (0 "SCULPTOR" "AW MAN...")) -("BIR-AM01" :hint 0 (0 "BIRDWATCHER" "ARE YOU NEAR THE EGG YET?")) -("BIR-AM02" :hint 0 (0 "BIRDWATCHER" "THAT'S IT, JUST A LITTLE FURTHER!")) -("BIR-AM03" :hint 0 (0 "BIRDWATCHER" "OH, GOOD, NOW PUSH IT. OH, GENTLY NOW!")) -("BIR-AM04" :hint 0 (0 "BIRDWATCHER" "OH, MY, THAT WASN'T GENTLE!")) -("BIR-AM05" :hint 0 (0 "BIRDWATCHER" "COME ON, PUSH THE EGG OFF THE CLIFF!")) -("BIR-AM06" :hint 0 (0 "BIRDWATCHER" "OHH!")) -("BIR-AM07" :hint 0 (0 "BIRDWATCHER" "OH, HERE, BIRDIE BIRDIE. HERE, BIRDIE BIRDIE.")) -("BIR-AM08" :hint 0 (0 "BIRDWATCHER" "HERE, BIRDIE BIRDIE.")) -("BIR-AM09" :hint 0 (0 "BIRDWATCHER" "BE CAREFUL, IT DOESN'T LOOK SAFE UP THERE!")) -("BIR-AM10" :hint 0 (0 "BIRDWATCHER" "CAREFUL, EASY DOES IT!")) -("BIR-AM11" :hint 0 (0 "BIRDWATCHER" "GOOD JOB! NOW MEET ME DOWN HERE BY THE EGG.")) -("BIR-AM12" :hint 0 (0 "BIRDWATCHER" "YOU MUST BE A TRUE ANIMAL LOVER!")) -("BIR-AM13" :hint 0 (0 "BIRDWATCHER" "DO YOU SEE ANY OTHER BIRDS UP THERE?")) -("BIR-LO01" :hint 0 (0 "BIRDWATCHER" "(KISS) (WHISTLE)")) -("BIR-LO02" :hint 0 (0 "BIRDWATCHER" "OH MY, THAT'S A PRETTY ONE.")) -("BIR-LO03" :hint 0 (0 "BIRDWATCHER" "OH LOOK, A MUCKY-MUCK.")) - - -("sksp0019" :hint #x236 (0 "DAXTER" "HEY, SEAGULLS! LET'S BUZZ 'EM FOR KICKS.")) -("sksp0026" :hint #x237 - (0 "DAXTER" "HEY! THAT PELICAN JUST SNAGGED A POWER CELL!") - (197) - (200 "DAXTER" "LET'S GO KICK SOME BIG BIRD BUTT!") +("BIR-AM01" :hint #x0 + (0 "BIRDWATCHER" "ARE YOU NEAR THE EGG YET?") ) -("sksp0030" :hint #x239 (0 "DAXTER" "HEY, TRY PUNCHING THE ROCKS TO GET 'EM OUT OF THE WAY!")) -("sksp0034" :hint #x23b (0 "DAXTER" "AWOOGA! AWOOGA! DIVE FOR THOSE ORBS, JAK, DIVE!")) -("sksp0020" :hint #x268 (0 "DAXTER" "YEAH HA HA! LET'S DO THAT AGAIN!")) -("sksp0022" :hint #x26a (0 "DAXTER" "YEAH HA HA HA HA HA!! WOO HO HO HO!")) -("sksp0023" :hint #x26b (0 "DAXTER" "WOOHOO!")) -("sksp0024" :hint #x26c (0 "DAXTER" "YEAH HA HA! YEAH!")) -("sksp0025" :hint #x273 (0 "DAXTER" "WHOA! THEY CAUSED AN AVALANCHE! LET'S CHECK IT OUT.")) -("sksp0027" :hint #x274 (0 "DAXTER" "QUICK! WE HAVE TO GET TO THE POWER CELL BEFORE THE PELICAN SCOOPS IT UP AGAIN.")) -("sksp0029" :hint #x275 - (0 "DAXTER" "EH, WE MIGHT WANNA TALK TO THE BIRDWATCHER") - (125 "DAXTER" "AND SEE IF WE SCRAMBLED THAT FLUT FLUT EGG.") - ) -("sagevb01" :hint #x288 - (0 "SAGE" "WELL, I SEE THAT YOU TWO HAVE FINALLY DECIDED TO UNBLOCK MY COLLECTORS.") - (347) - (352 "SAGE" "I WOULD OFFER MY CONGRATULATIONS, BUT YOU HAVE SO MUCH TO DO I WON'T WASTE YOUR TIME.") - (705) - (712 "SAGE" "BY THE WAY, IF THINGS DON'T WORK OUT,") - (884 "SAGE" "DAXTER COULD ALWAYS GET A JOB CONTROLLING THE VILLAGE RAT PROBLEM.") - (1116 "SAGE" "NYEH HA HA HA HA.") - ) -("sksp0443" :hint #x2af (0 "DAXTER" "PUNCH THOSE POLES UP FROM BELOW!")) +("BIR-AM02" :hint #x0 + (0 "BIRDWATCHER" "THAT'S IT, JUST A LITTLE FURTHER!") + ) + +("BIR-AM03" :hint #x0 + (0 "BIRDWATCHER" "OH, GOOD, NOW PUSH IT. OH, GENTLY NOW!") + ) + +("BIR-AM04" :hint #x0 + (0 "BIRDWATCHER" "OH, MY, THAT WASN'T GENTLE!") + ) + +("BIR-AM05" :hint #x0 + (0 "BIRDWATCHER" "COME ON, PUSH THE EGG OFF THE CLIFF!") + ) + +("BIR-AM06" :hint #x0 + (0 "BIRDWATCHER" "OHH!") + ) + +("BIR-AM07" :hint #x0 + (0 "BIRDWATCHER" "OH, HERE, BIRDIE BIRDIE. HERE, BIRDIE BIRDIE.") + ) + +("BIR-AM08" :hint #x0 + (0 "BIRDWATCHER" "HERE, BIRDIE BIRDIE.") + ) + +("BIR-AM09" :hint #x0 + (0 "BIRDWATCHER" "BE CAREFUL, IT DOESN'T LOOK SAFE UP THERE!") + ) + +("BIR-AM10" :hint #x0 + (0 "BIRDWATCHER" "CAREFUL, EASY DOES IT!") + ) + +("BIR-AM11" :hint #x0 + (0 "BIRDWATCHER" "GOOD JOB! NOW MEET ME DOWN HERE BY THE EGG.") + ) + +("BIR-AM12" :hint #x0 + (0 "BIRDWATCHER" "YOU MUST BE A TRUE ANIMAL LOVER!") + ) + +("BIR-AM13" :hint #x0 + (0 "BIRDWATCHER" "DO YOU SEE ANY OTHER BIRDS UP THERE?") + ) + +("BIR-LO01" :hint #x0 + (0 "BIRDWATCHER" "(KISS) (WHISTLE)") + ) + +("BIR-LO02" :hint #x0 + (0 "BIRDWATCHER" "OH MY, THAT'S A PRETTY ONE.") + ) + +("BIR-LO03" :hint #x0 + (0 "BIRDWATCHER" "OH LOOK, A MUCKY-MUCK.") + ) + +("CHI-AM01" :hint #x0 + (0 "MAYOR" "THIS IS A CATASTROPHE. A CATASTROPHE I SAY!") + ) + +("CHI-AM02" :hint #x0 + (0 "MAYOR" "THEY WANT ME TO GO INTO THE JUNGLE - ME! HO HO OH...") + (214) + (219 "MAYOR" "I'D SOONER WRESTLE AN ENRAGED FLUT FLUT.") + ) + +("CHI-AM03" :hint #x0 + (0 "MAYOR" "...FIRST THE FISHERMAN'S BOAT, (STUTTERS) IT'S ATTACKED BY A MONSTER...") + (352) + (395 "MAYOR" "AND NOW, NOW THIS... WHAT ELSE COULD GO WRONG?") + ) + +("CHI-AM04" :hint #x0 + (0 "MAYOR" "I DON'T... I'D, I'D... OOH, MAYBE I SHOULD RAISE TAXES (STUTTERS) TO PAY FOR THIS MESS.") + ) + +("CHI-AM05" :hint #x0 + (0 "MAYOR" "PROBLEMS... PROBLEMS... PROBLEMS!") + ) + +("CHI-AM06" :hint #x0 + (0 "MAYOR" "(SOBS) I'LL NEVER GET RE-ELECTED NOW...") + ) + +("CHI-AM07" :hint #x0 + (0 "MAYOR" "(MOANS) WHAT'S HAPPENED TO THE VILLAGE'S ENERGY BEAM?") + ) + +("CHI-AM08" :hint #x0 + (0 "MAYOR" "I... THEY, I DON'T... (MUTTERS)") + ) + +("CHI-LO01" :hint #x0 + (0 "MAYOR" "...WINDMILL, MONSTERS, POWER, RE-ELECTION...") + ) + +("CHI-LO02" :hint #x0 + (0 "MAYOR" "...THE POLLS, I... MONSTERS, HM... RE-ELECTION, OH...") + ) + +("SCU-AM01" :hint #x0 + (0 "SCULPTOR" "AW... I JUST CAN'T DO IT ANYMORE.") + ) + +("SCU-AM02" :hint #x0 + (0 "SCULPTOR" "AW, I WISH I HAD SOME INSPIRATION.") + ) + +("SCU-AM03" :hint #x0 + (0 "SCULPTOR" "I'M AS INSPIRED AS... THIS ROCK.") + ) + +("SCU-AM04" :hint #x0 + (0 "SCULPTOR" "AW... WHERE IS MY MUSE?") + ) + +("SCU-AM05" :hint #x0 + (0 "SCULPTOR" "I CAN'T WORK LIKE THIS!") + ) + +("SCU-AM06" :hint #x0 + (0 "SCULPTOR" "AW MAN... WILL SHE EVER COME BACK TO ME?") + ) + +("SCU-LO01" :hint #x0 + (0 "SCULPTOR" "AW MAN...") + ) + +("bird-lady-beach-resolution" + (30 "BIRDWATCHER" "OH MY, I HOPE THE POOR DEAR'S OKAY. HERE'S A POWER CELL FOR YOUR VALOR.") + (243) + (306 "FLUT FLUT" "MAMA!") + (354) + (406 "FLUT FLUT" "MAMA!") + (430 "DAXTER" "OH NO! NO, NO, NO, NO!") + (533) + (535 "BIRDWATCHER" "LOOK... ISN'T THAT CUTE? IT THINKS YOU'RE ITS MAMA.") + (696) + (699 "DAXTER" "EH? I'M NOT YOUR MOM! YOU SEE ANY FEATHERS HERE?") + (803) + (807 "BIRDWATCHER" "OH, LOVE AT FIRST SIGHT! AH...") + (936) + (939 "BIRDWATCHER" "LISTEN, BOYS, I'LL TAKE THIS LITTLE CHICK BACK TO THE VILLAGE WITH ME") + (1054 "BIRDWATCHER" "AND WORK WITH THE SAGE TO TAKE CARE OF HER.") + ) + +("bird-lady-introduction" + (125 "BIRDWATCHER" "OH MY, WHAT A HORRIBLY SICK LITTLE BIRD.") + (245) + (251 "DAXTER" "HUH! YOU DON'T LOOK SO GOOD YOURSELF, LADY!") + (326) + (331 "BIRDWATCHER" "OH, SORRY. I THOUGHT YOU WERE A SPOTTED ORANGE-BELLIED RAIN FRAY.") + (454) + (457 "BIRDWATCHER" "YOU KNOW, YESTERDAY I SAW SOME TERRIBLY VICIOUS CREATURES") + (578 "BIRDWATCHER" "CAPTURE A MOTHER FLUT FLUT NEAR THE BEACH.") + (648) + (654 :offscreen "BIRDWATCHER" "NOW THERE'S THIS POOR LITTLE ORPHAN EGG SITTING IN A NEST AT THE TOP OF THE CLIFF") + (796 :offscreen "BIRDWATCHER" "AND I CAN'T GET TO IT. IF YOU COULD CLIMB UP THERE AND PUSH IT OFF, I'VE PILED") + (951 :offscreen "BIRDWATCHER" "SOME HAY DOWN AT THE BASE TO CATCH IT SAFELY.") + (1041) + (1044 "BIRDWATCHER" "DO AN OLD LADY A FAVOR, AND I'LL GIVE YOU A POWER CELL.") + (1198) + ) + +("bird-lady-reminder-1" + (56 "BIRDWATCHER" "OH, HELLO AGAIN. DID YOU BOYS FIND THAT BLUE EGG ON THE CLIFF?") + (208) + (211 "BIRDWATCHER" "PUSH IT OFF THE EDGE AND I'LL GIVE YOU A POWER CELL!") + (348) + ) + +("bird-lady-reminder-2" + (58 "BIRDWATCHER" "ARE YOU BOYS STILL PICKING AROUND HERE? HO HO HO.") + (170) + (174 "BIRDWATCHER" "I'LL BET THAT POOR LITTLE BLUE EGG ON THE CLIFF IS GETTING AWFULLY COLD BY NOW.") + (313) + (316 "BIRDWATCHER" "GO SAVE IT BY PUSHING IT OFF THE EDGE OF THE CLIFF, AND I'LL GIVE YOU A POWER CELL.") + ) ("mayor-introduction" (31 "MAYOR" "NO... (MUTTERS) DON'T TELL ME THAT YOU TWO HAVE PROBLEMS AS WELL!") @@ -547,6 +813,16 @@ (365 "MAYOR" "I, WH-WH-WH-WHY I JUST HOPE THIS POWER CELL ADEQUATELY REPRESENTS MY GRATITUDE.") ) +("sagevb01" :hint #x288 + (0 "SAGE" "WELL, I SEE THAT YOU TWO HAVE FINALLY DECIDED TO UNBLOCK MY COLLECTORS.") + (347) + (352 "SAGE" "I WOULD OFFER MY CONGRATULATIONS, BUT YOU HAVE SO MUCH TO DO I WON'T WASTE YOUR TIME.") + (705) + (712 "SAGE" "BY THE WAY, IF THINGS DON'T WORK OUT,") + (884 "SAGE" "DAXTER COULD ALWAYS GET A JOB CONTROLLING THE VILLAGE RAT PROBLEM.") + (1116 "SAGE" "NYEH HA HA HA HA.") + ) + ("sculptor-introduction" (30 "SCULPTOR" "HEY... LITTLE FURRY DUDE!") (154) @@ -587,119 +863,156 @@ (230 "SCULPTOR" "HERE, TAKE THIS POWER CELL. I WON'T NEED IT NOW THAT I HAVE MY INSPIRATION BACK!") ) -("bird-lady-introduction" - (125 "BIRDWATCHER" "OH MY, WHAT A HORRIBLY SICK LITTLE BIRD.") - (245) - (251 "DAXTER" "HUH! YOU DON'T LOOK SO GOOD YOURSELF, LADY!") - (326) - (331 "BIRDWATCHER" "OH, SORRY. I THOUGHT YOU WERE A SPOTTED ORANGE-BELLIED RAIN FRAY.") - (454) - (457 "BIRDWATCHER" "YOU KNOW, YESTERDAY I SAW SOME TERRIBLY VICIOUS CREATURES") - (578 "BIRDWATCHER" "CAPTURE A MOTHER FLUT FLUT NEAR THE BEACH.") - (648) - (654 :offscreen "BIRDWATCHER" "NOW THERE'S THIS POOR LITTLE ORPHAN EGG SITTING IN A NEST AT THE TOP OF THE CLIFF") - (796 :offscreen "BIRDWATCHER" "AND I CAN'T GET TO IT. IF YOU COULD CLIMB UP THERE AND PUSH IT OFF, I'VE PILED") - (951 :offscreen "BIRDWATCHER" "SOME HAY DOWN AT THE BASE TO CATCH IT SAFELY.") - (1041) - (1044 "BIRDWATCHER" "DO AN OLD LADY A FAVOR, AND I'LL GIVE YOU A POWER CELL.") - (1198) +("sksp0019" :hint #x236 + (0 "DAXTER" "HEY, SEAGULLS! LET'S BUZZ 'EM FOR KICKS.") ) -("bird-lady-reminder-1" - (56 "BIRDWATCHER" "OH, HELLO AGAIN. DID YOU BOYS FIND THAT BLUE EGG ON THE CLIFF?") - (208) - (211 "BIRDWATCHER" "PUSH IT OFF THE EDGE AND I'LL GIVE YOU A POWER CELL!") - (348) +("sksp0020" :hint #x268 + (0 "DAXTER" "YEAH HA HA! LET'S DO THAT AGAIN!") ) -("bird-lady-reminder-2" - (58 "BIRDWATCHER" "ARE YOU BOYS STILL PICKING AROUND HERE? HO HO HO.") - (170) - (174 "BIRDWATCHER" "I'LL BET THAT POOR LITTLE BLUE EGG ON THE CLIFF IS GETTING AWFULLY COLD BY NOW.") - (313) - (316 "BIRDWATCHER" "GO SAVE IT BY PUSHING IT OFF THE EDGE OF THE CLIFF, AND I'LL GIVE YOU A POWER CELL.") +("sksp0022" :hint #x26a + (0 "DAXTER" "YEAH HA HA HA HA HA!! WOO HO HO HO!") ) -("bird-lady-beach-resolution" - (30 "BIRDWATCHER" "OH MY, I HOPE THE POOR DEAR'S OKAY. HERE'S A POWER CELL FOR YOUR VALOR.") - (243) - (306 "FLUT FLUT" "MAMA!") - (354) - (406 "FLUT FLUT" "MAMA!") - (430 "DAXTER" "OH NO! NO, NO, NO, NO!") - (533) - (535 "BIRDWATCHER" "LOOK... ISN'T THAT CUTE? IT THINKS YOU'RE ITS MAMA.") - (696) - (699 "DAXTER" "EH? I'M NOT YOUR MOM! YOU SEE ANY FEATHERS HERE?") - (803) - (807 "BIRDWATCHER" "OH, LOVE AT FIRST SIGHT! AH...") - (936) - (939 "BIRDWATCHER" "LISTEN, BOYS, I'LL TAKE THIS LITTLE CHICK BACK TO THE VILLAGE WITH ME") - (1054 "BIRDWATCHER" "AND WORK WITH THE SAGE TO TAKE CARE OF HER.") +("sksp0023" :hint #x26b + (0 "DAXTER" "WOOHOO!") ) +("sksp0024" :hint #x26c + (0 "DAXTER" "YEAH HA HA! YEAH!") + ) + +("sksp0025" :hint #x273 + (0 "DAXTER" "WHOA! THEY CAUSED AN AVALANCHE! LET'S CHECK IT OUT.") + ) + +("sksp0026" :hint #x237 + (0 "DAXTER" "HEY! THAT PELICAN JUST SNAGGED A POWER CELL!") + (197) + (200 "DAXTER" "LET'S GO KICK SOME BIG BIRD BUTT!") + ) + +("sksp0027" :hint #x274 + (0 "DAXTER" "QUICK! WE HAVE TO GET TO THE POWER CELL BEFORE THE PELICAN SCOOPS IT UP AGAIN.") + ) + +("sksp0029" :hint #x275 + (0 "DAXTER" "EH, WE MIGHT WANNA TALK TO THE BIRDWATCHER") + (125 "DAXTER" "AND SEE IF WE SCRAMBLED THAT FLUT FLUT EGG.") + ) + +("sksp0030" :hint #x239 + (0 "DAXTER" "HEY, TRY PUNCHING THE ROCKS TO GET 'EM OUT OF THE WAY!") + ) + +("sksp0034" :hint #x23b + (0 "DAXTER" "AWOOGA! AWOOGA! DIVE FOR THOSE ORBS, JAK, DIVE!") + ) + +("sksp0443" :hint #x2af + (0 "DAXTER" "PUNCH THOSE POLES UP FROM BELOW!") + ) ;; ----------------- ;; jungle +;; ----------------- +("FIS-AM01" :hint #x0 + (0 "FISHERMAN" "GRR, THESE DARN FISH... I NEVER CATCH ME A SINGLE ONE.") + ) -("FIS-AM01" :hint 0 (0 "FISHERMAN" "GRR, THESE DARN FISH... I NEVER CATCH ME A SINGLE ONE.")) -("FIS-AM02" :hint 0 (0 "FISHERMAN" "THEM MONSTERS THAT DONE BIT ME SHIP WILL DRIVE ME BROKE.")) -("FIS-AM03" :hint 0 +("FIS-AM02" :hint #x0 + (0 "FISHERMAN" "THEM MONSTERS THAT DONE BIT ME SHIP WILL DRIVE ME BROKE.") + ) + +("FIS-AM03" :hint #x0 (0 "FISHERMAN" "DRAT, THESE BLIGHTERS!") (135 "FISHERMAN" "IN ME BASKET, DARN FISHIES, COME ON YEAH...") (419) (425 "FISHERMAN" "HEH HE HE HE HE...") ) -("FIS-AM04" :hint 0 (0 "FISHERMAN" "OH I LOVE THE SMELL OF FISH IN THE MORNIN'.")) -("FIS-AM05" :hint 0 (0 "FISHERMAN" "OOH I COULD USE ME A SALTED FISH LIPS ON RYE.")) -("FIS-AM06" :hint 0 (0 "FISHERMAN" "I 'MEMBER THE BIG ONE THAT GOT AWAY...")) -("FIS-LO01" :hint 0 (0 "FISHERMAN" "GRR!")) -("FIS-LO03" :hint 0 (0 "FISHERMAN" "HA HA HA HA HA HAH!")) -("FIS-LO04" :hint 0 (0 "FISHERMAN" "DARN FISH... THEY NEVER GET IN ME NET!")) -("FIS-LO05" :hint 0 (0 "FISHERMAN" "HA HA HA AH HA HA HA HAH!")) -("FIS-TA01" :hint 0 (0 "FISHERMAN" "TO THE LEFT!")) -("FIS-TA02" :hint 0 (0 "FISHERMAN" "RIGHT WITH YA!")) -("FIS-TA1A" :hint 0 (0 "FISHERMAN" "LEFT!")) -("FIS-TA2A" :hint 0 (0 "FISHERMAN" "RIGHT!")) -("FIS-TA03" :hint 0 (0 "FISHERMAN" "HERE COMES A BIG ONE!")) -("FIS-TA04" :hint 0 (0 "FISHERMAN" "YE MISSED A JUMBO, LADDIE!")) -("FIS-TA05" :hint 0 (0 "FISHERMAN" "DON'T MISS ONE LIKE THAT AGAIN!")) -("FIS-TA06" :hint 0 (0 "FISHERMAN" "STEADY BOY.")) -("FIS-TA07" :hint 0 (0 "FISHERMAN" "YOUR SAILS ARE SAGGING IN THE WIND, BOY.")) -("FIS-TA08" :hint 0 (0 "FISHERMAN" "MISSED.")) -("FIS-TA09" :hint 0 (0 "FISHERMAN" "LOOKS LIKE YOU COULD USE A BIGGER NET...")) -("FIS-TA10" :hint 0 (0 "FISHERMAN" "MISSED AGAIN!")) -("FIS-TA11" :hint 0 (0 "FISHERMAN" "HOLD STEADY! YOU'RE ALMOST THERE...")) +("FIS-AM04" :hint #x0 + (0 "FISHERMAN" "OH I LOVE THE SMELL OF FISH IN THE MORNIN'.") + ) -("sksp0038" :hint #x23c - (0 "DAXTER" "HMM, IF BLUE ECO CAN BUILD A BRIDGE, THEN I BET IT'LL OPEN A DOOR!") - (275) - (281 "DAXTER" "LET'S GO BACK AND GET YOU JUICED UP AGAIN.") +("FIS-AM05" :hint #x0 + (0 "FISHERMAN" "OOH I COULD USE ME A SALTED FISH LIPS ON RYE.") ) -("sksp0040" :hint #x23d (0 "DAXTER" "LET'S GET UP ON THAT MACHINE AND BREAK THE MIRROR DIVERTING THE PRECURSOR BEAM!")) -("sksp0041" :hint #x23e (0 "DAXTER" "WE NEED TO GET TO THE TOP OF THAT TOWER!")) -("sksp0011" :hint #x240 - (0 "DAXTER" "WE SHOULD ASK THE FISHERMAN DOWN BY THE JUNGLE RIVER") - (136 "DAXTER" "IF WE CAN BORROW HIS SPEEDBOAT TO ZOOM ON OVER TO MISTY ISLAND!") + +("FIS-AM06" :hint #x0 + (0 "FISHERMAN" "I 'MEMBER THE BIG ONE THAT GOT AWAY...") ) -("sksp0039" :hint #x25b - (0 "DAXTER" "HEY, THERE'S LITTLE LIGHTNING MARKS ON THOSE POSTS") - (202 "DAXTER" "AND THERE'S LIGHTNING COMING OUT OF THAT VENT OVER THERE.") - (391) - (400 "DAXTER" "ARE YOU THINKING WHAT I'M THINKING?") + +("FIS-LO01" :hint #x0 + (0 "FISHERMAN" "GRR!") ) -("sksp0018" :hint #x25e - (0 "DAXTER" "WE SHOULD GO TELL THAT WINDBAG OF A MAYOR THAT HE OWES US BIG TIME") - (223 "DAXTER" "FOR CONNECTING THE VILLAGE ENERGY BEAM!") + +("FIS-LO03" :hint #x0 + (0 "FISHERMAN" "HA HA HA HA HA HAH!") ) -("sksp0037" :hint #x25f - (0 "DAXTER" "THOSE TOWER DOOHICKEYS SCATTERED ALL OVER THE JUNGLE MUST REDIRECT THE ECO BEAM.") - (214) - (220 "DAXTER" "LET'S GO FIDDLE WITH THEM!") + +("FIS-LO04" :hint #x0 + (0 "FISHERMAN" "DARN FISH... THEY NEVER GET IN ME NET!") ) -("sksp0b42" :hint #x278 (0 "DAXTER" "STOP MISSING THE YELLOW FISH, THEY WEIGH FIVE POUNDS EACH! AND THAT'S A LOTTA FISH, JAK.")) + +("FIS-LO05" :hint #x0 + (0 "FISHERMAN" "HA HA HA AH HA HA HA HAH!") + ) + +("FIS-TA01" :hint #x0 + (0 "FISHERMAN" "TO THE LEFT!") + ) + +("FIS-TA02" :hint #x0 + (0 "FISHERMAN" "RIGHT WITH YA!") + ) + +("FIS-TA03" :hint #x0 + (0 "FISHERMAN" "HERE COMES A BIG ONE!") + ) + +("FIS-TA04" :hint #x0 + (0 "FISHERMAN" "YE MISSED A JUMBO, LADDIE!") + ) + +("FIS-TA05" :hint #x0 + (0 "FISHERMAN" "DON'T MISS ONE LIKE THAT AGAIN!") + ) + +("FIS-TA06" :hint #x0 + (0 "FISHERMAN" "STEADY BOY.") + ) + +("FIS-TA07" :hint #x0 + (0 "FISHERMAN" "YOUR SAILS ARE SAGGING IN THE WIND, BOY.") + ) + +("FIS-TA08" :hint #x0 + (0 "FISHERMAN" "MISSED.") + ) + +("FIS-TA09" :hint #x0 + (0 "FISHERMAN" "LOOKS LIKE YOU COULD USE A BIGGER NET...") + ) + +("FIS-TA10" :hint #x0 + (0 "FISHERMAN" "MISSED AGAIN!") + ) + +("FIS-TA11" :hint #x0 + (0 "FISHERMAN" "HOLD STEADY! YOU'RE ALMOST THERE...") + ) + +("FIS-TA1A" :hint #x0 + (0 "FISHERMAN" "LEFT!") + ) + +("FIS-TA2A" :hint #x0 + (0 "FISHERMAN" "RIGHT!") + ) + ("asstvb02" :hint #x289 (0 "KEIRA" "WOW, DID YOU SEE THAT? BLUE ECO VENTS HAVE BEEN ACTIVATED ALL OVER THE WORLD!") (305) @@ -707,13 +1020,15 @@ (435) (442 "KEIRA" "THERE MUST BE PLACES TO TURN ON THE OTHER ECO VENTS AS WELL.") ) -("sksp0049" :hint #x29c (0 "DAXTER" "LINE UP THE BEAM BY POINTING IT AT THE NEXT TOWER!")) -("sksp0050" :hint #x29d (0 "DAXTER" "BREAK THE MIRROR, JAK!")) -("sksp0051" :hint #x29e (0 "DAXTER" "GET THE BUGS, JAK! GET THE BUGS!")) -("sksp0052" :hint #x29f (0 "DAXTER" "LET'S GO TO THE NEXT TOWER AND RECONNECT THE BEAM THERE!")) -("sksp0053" :hint #x2a0 (0 "DAXTER" "HEY! WE CAN FOLLOW THE BEAM TO FIND THE NEXT TOWER.")) -("sksp0054" :hint #x2a1 (0 "DAXTER" "WE NEED TO CHARGE YOU UP WITH THAT BLUE STUFF TO GET THIS OPEN!")) +("fisher-accept" + (0 :offscreen "FISHERMAN" "THERE ARE TWO TYPES OF GOOD FISH TO CATCH: ONE-POUND FISHIES, AND FIVE-POUND FISHIES.") + (220 :offscreen "FISHERMAN" "HEH. IF YOU MISS 20 POUNDS OF GOOD FISH, THEN I'M GONNA TAKE ME NET BACK FROM YA!") + (432) + (438 :offscreen "FISHERMAN" "THERE ARE POISONOUS EELS IN THIS RIVER.") + (542) + (545 :offscreen "FISHERMAN" "CATCH EVEN ONE OF THEM BOOGERS, AND YOU'LL POISON THE WHOLE DARN CATCH!") + ) ("fisher-introduction" (0 "DAXTER" "WHAT DO YOU HAVE IN THE BASKET?") @@ -736,21 +1051,12 @@ (961 "FISHERMAN" "YOU'S WANT TO TRY THE CHALLENGE?") ) -("fisher-reminder-1" - (6 "FISHERMAN" "WANT TO TRY AND BEAT THE RIVER, DO YA?") - ) - ("fisher-reject" (2 "FISHERMAN" "WELL, IF YOU WANT TO TRY FOR THE POWER CELL SOMETIME, YOU KNOW WHERE TO FIND ME.") ) -("fisher-accept" - (0 :offscreen "FISHERMAN" "THERE ARE TWO TYPES OF GOOD FISH TO CATCH: ONE-POUND FISHIES, AND FIVE-POUND FISHIES.") - (220 :offscreen "FISHERMAN" "HEH. IF YOU MISS 20 POUNDS OF GOOD FISH, THEN I'M GONNA TAKE ME NET BACK FROM YA!") - (432) - (438 :offscreen "FISHERMAN" "THERE ARE POISONOUS EELS IN THIS RIVER.") - (542) - (545 :offscreen "FISHERMAN" "CATCH EVEN ONE OF THEM BOOGERS, AND YOU'LL POISON THE WHOLE DARN CATCH!") +("fisher-reminder-1" + (6 "FISHERMAN" "WANT TO TRY AND BEAT THE RIVER, DO YA?") ) ("fisher-resolution" @@ -762,26 +1068,81 @@ (351 "FISHERMAN" "WHENEVER YOU LIKE!") ) +("sksp0011" :hint #x240 + (0 "DAXTER" "WE SHOULD ASK THE FISHERMAN DOWN BY THE JUNGLE RIVER") + (136 "DAXTER" "IF WE CAN BORROW HIS SPEEDBOAT TO ZOOM ON OVER TO MISTY ISLAND!") + ) + +("sksp0018" :hint #x25e + (0 "DAXTER" "WE SHOULD GO TELL THAT WINDBAG OF A MAYOR THAT HE OWES US BIG TIME") + (223 "DAXTER" "FOR CONNECTING THE VILLAGE ENERGY BEAM!") + ) + +("sksp0037" :hint #x25f + (0 "DAXTER" "THOSE TOWER DOOHICKEYS SCATTERED ALL OVER THE JUNGLE MUST REDIRECT THE ECO BEAM.") + (214) + (220 "DAXTER" "LET'S GO FIDDLE WITH THEM!") + ) + +("sksp0038" :hint #x23c + (0 "DAXTER" "HMM, IF BLUE ECO CAN BUILD A BRIDGE, THEN I BET IT'LL OPEN A DOOR!") + (275) + (281 "DAXTER" "LET'S GO BACK AND GET YOU JUICED UP AGAIN.") + ) + +("sksp0039" :hint #x25b + (0 "DAXTER" "HEY, THERE'S LITTLE LIGHTNING MARKS ON THOSE POSTS") + (202 "DAXTER" "AND THERE'S LIGHTNING COMING OUT OF THAT VENT OVER THERE.") + (391) + (400 "DAXTER" "ARE YOU THINKING WHAT I'M THINKING?") + ) + +("sksp0040" :hint #x23d + (0 "DAXTER" "LET'S GET UP ON THAT MACHINE AND BREAK THE MIRROR DIVERTING THE PRECURSOR BEAM!") + ) + +("sksp0041" :hint #x23e + (0 "DAXTER" "WE NEED TO GET TO THE TOP OF THAT TOWER!") + ) + +("sksp0049" :hint #x29c + (0 "DAXTER" "LINE UP THE BEAM BY POINTING IT AT THE NEXT TOWER!") + ) + +("sksp0050" :hint #x29d + (0 "DAXTER" "BREAK THE MIRROR, JAK!") + ) + +("sksp0051" :hint #x29e + (0 "DAXTER" "GET THE BUGS, JAK! GET THE BUGS!") + ) + +("sksp0052" :hint #x29f + (0 "DAXTER" "LET'S GO TO THE NEXT TOWER AND RECONNECT THE BEAM THERE!") + ) + +("sksp0053" :hint #x2a0 + (0 "DAXTER" "HEY! WE CAN FOLLOW THE BEAM TO FIND THE NEXT TOWER.") + ) + +("sksp0054" :hint #x2a1 + (0 "DAXTER" "WE NEED TO CHARGE YOU UP WITH THAT BLUE STUFF TO GET THIS OPEN!") + ) + +("sksp0b42" :hint #x278 + (0 "DAXTER" "STOP MISSING THE YELLOW FISH, THEY WEIGH FIVE POUNDS EACH! AND THAT'S A LOTTA FISH, JAK.") + ) ;; ----------------- ;; misty +;; ----------------- +("asstvb03" :hint #x28b + (0 "KEIRA" "GOOD! YOU STOPPED ALL THE MINE-DROPPING LURKERS!") + (190 "KEIRA" "THEY'VE BEEN THREATENING THE WATERS AROUND OUR VILLAGE FOR WEEKS.") + (391 "KEIRA" "BRING YOUR ZOOMER BACK TO THE TRANS-PAD AND I'LL TELEPORT IT BACK.") + ) -("sksp0031" :hint #x23a (0 "DAXTER" "OOH! LET'S USE THE CANNON TO BLOW THINGS UP!")) -("sksp0064" :hint #x245 (0 "DAXTER" "WATCH YOUR BACK! YOU REMEMBER WHAT HAPPENED THE LAST TIME WE WERE HERE.")) -("sksp0067" :hint #x249 (0 "DAXTER" "DON'T FALL INTO THE MIST BELOW US! 'CAUSE I DON'T THINK WE'LL MAKE IT BACK.")) -("sksp0059" :hint #x26e (0 "DAXTER" "THIS PLACE GIVES ME THE WILLIES! LET'S KEEP YOU OUT OF THE OOZE, OKAY?")) -("sksp0060" :hint #x26f - (0 "DAXTER" "(WHIMPERS) THIS PLACE GIVES ME THE CREEPS! AND TRUST ME, IT'S A WHOLE NEW EXPERIENCE") - (337 "DAXTER" "WHEN YOU'RE COVERED IN FUZZ!") - ) -("sksp0056" :hint #x27b (0 "DAXTER" "HEY! I SEE THE SCULPTOR'S MUSE!")) -("sksp0062" :hint #x27e (0 "DAXTER" "LET'S PLOW INTO THOSE BALLOON LURKERS AND SHRED 'EM!")) -("sksp0063" :hint #x27f - (0 "DAXTER" "I SAID \"SHRED THE LURKERS,\" JAK. NOT THE MINES!") - (332) - (344 "DAXTER" "RULE NUMBER ONE: ALWAYS AVOID THE MINES!") - ) ("sagevb02" :hint #x28a (6 "SAGE" "I HAVE TO ADMIT, I'M IMPRESSED. YOU TWO DIDN'T SCREW UP!") (385) @@ -791,53 +1152,57 @@ (880 "SAGE" "WIPE THAT RIDICULOUS GRIN OFF YOUR FACE, DAXTER! THE TWO OF YOU HAVE PLENTY TO DO.") (1191 "SAGE" "GET ON WITH IT!") ) -("asstvb03" :hint #x28b - (0 "KEIRA" "GOOD! YOU STOPPED ALL THE MINE-DROPPING LURKERS!") - (190 "KEIRA" "THEY'VE BEEN THREATENING THE WATERS AROUND OUR VILLAGE FOR WEEKS.") - (391 "KEIRA" "BRING YOUR ZOOMER BACK TO THE TRANS-PAD AND I'LL TELEPORT IT BACK.") - ) -("sksp0069" :hint #x2a3 (0 "DAXTER" "IT'S AN AMBUSH, JAK! IT'S AN AMBUSH!")) -("sksp0070" :hint #x2a4 (0 "DAXTER" "JUMP, THEN DIVE ONTO THE TEETER-TOTTER.")) -("sksp0435" :hint #x2aa (0 "DAXTER" "KNOCK OVER THOSE BONES!")) +("sksp0031" :hint #x23a + (0 "DAXTER" "OOH! LET'S USE THE CANNON TO BLOW THINGS UP!") + ) + +("sksp0056" :hint #x27b + (0 "DAXTER" "HEY! I SEE THE SCULPTOR'S MUSE!") + ) + +("sksp0059" :hint #x26e + (0 "DAXTER" "THIS PLACE GIVES ME THE WILLIES! LET'S KEEP YOU OUT OF THE OOZE, OKAY?") + ) + +("sksp0060" :hint #x26f + (0 "DAXTER" "(WHIMPERS) THIS PLACE GIVES ME THE CREEPS! AND TRUST ME, IT'S A WHOLE NEW EXPERIENCE") + (337 "DAXTER" "WHEN YOU'RE COVERED IN FUZZ!") + ) + +("sksp0062" :hint #x27e + (0 "DAXTER" "LET'S PLOW INTO THOSE BALLOON LURKERS AND SHRED 'EM!") + ) + +("sksp0063" :hint #x27f + (0 "DAXTER" "I SAID \"SHRED THE LURKERS,\" JAK. NOT THE MINES!") + (332) + (344 "DAXTER" "RULE NUMBER ONE: ALWAYS AVOID THE MINES!") + ) + +("sksp0064" :hint #x245 + (0 "DAXTER" "WATCH YOUR BACK! YOU REMEMBER WHAT HAPPENED THE LAST TIME WE WERE HERE.") + ) + +("sksp0067" :hint #x249 + (0 "DAXTER" "DON'T FALL INTO THE MIST BELOW US! 'CAUSE I DON'T THINK WE'LL MAKE IT BACK.") + ) + +("sksp0069" :hint #x2a3 + (0 "DAXTER" "IT'S AN AMBUSH, JAK! IT'S AN AMBUSH!") + ) + +("sksp0070" :hint #x2a4 + (0 "DAXTER" "JUMP, THEN DIVE ONTO THE TEETER-TOTTER.") + ) + +("sksp0435" :hint #x2aa + (0 "DAXTER" "KNOCK OVER THOSE BONES!") + ) ;; ----------------- ;; firecanyon - - -("asstvb09" :hint #x24f - (0 "KEIRA" "YOU DON'T HAVE ENOUGH POWER CELLS TO FUEL MY HEAT SHIELD.") - (216) - (234 "KEIRA" "YOU CAN'T CROSS FIRE CANYON UNTIL YOU COLLECT ENOUGH POWER CELLS.") - ) - -("sksp0076" :hint #x501 (0 "DAXTER" "JAK, HIT SOME JUMPS TO KEEP US OFF THE HOT GROUND!")) -("sksp0077" :hint #x502 (0 "DAXTER" "BALLOONS GOOD, BURNING HOT MAGMA BAD!")) -("sksp0078" :hint #x503 (0 "DAXTER" "RIDE ON THE RAISED PRECURSOR STUFF TO KEEP US COOL.")) -("sksp0079" :hint #x504 - (0 "DAXTER" "AHH! I'M GONNA DIE! I'M A COMING, GRANDPAP!") - (257) - (270 "DAXTER" "YOU HAVE TO GET THIS THING TO THE OTHER SIDE BEFORE IT OVERHEATS!") - ) -("sksp0080" :hint #x505 (0 "DAXTER" "USE THE HOP-TURN TO STEER HARDER!")) -("sksp0081" :hint #x506 (0 "DAXTER" "AHH! MAYBE I SHOULD DRIVE!")) -("sksp0082" :hint #x507 (0 "DAXTER" "YOU ARE TRYING TO AVOID THOSE DARK ECO BOXES, RIGHT?")) -("sksp0083" :hint #x508 (0 "DAXTER" "OOH! SEE IF WE CAN CATCH AIR OFF THOSE LURKERS!")) -("sksp0084" :hint #x509 (0 "DAXTER" "TOO CLOSE, TOO CLOSE!")) -("sksp0085" :hint #x50a (0 "DAXTER" "WHOA! THIS BABY'S GETTING WAY TOO HOT!")) -("sksp0086" :hint #x50b (0 "DAXTER" "A FEW MORE SECONDS AND WE'RE FRIED FLUT FLUT!")) - -("sksp0087" :hint #x50d (0 "DAXTER" "HIT THOSE BALLOONS TO COOL OFF!")) -("sksp0088" :hint #x50e (0 "DAXTER" "HERE COMES ANOTHER BALLOON!")) -("sksp0089" :hint #x50f (0 "DAXTER" "I WISH I SLEPT IN THIS MORNING!")) -("sksp0090" :hint #x510 (0 "DAXTER" "YOU MISSED A BALLOON! WE NEED THOSE OR WE'RE COOKED!")) -("sksp0091" :hint #x511 (0 "DAXTER" "THIS PUPPY'S GETTING TOO HOT!")) -("sksp0092" :hint #x512 (0 "DAXTER" "DO YOU SMELL SOMETHING BURNING?!")) -("sksp0093" :hint #x513 (0 "DAXTER" "I THINK MY TAIL'S ON FIRE!")) -("sksp0094" :hint #x514 (0 "DAXTER" "SHE'S GONNA BLOW!")) -("sksp0095" :hint #x515 (0 "DAXTER" "WAHOO! WE MADE IT!")) -("sksp0096" :hint #x516 (0 "DAXTER" "THERE'S SCOUT FLIES OUT HERE, TOO!")) - +;; ----------------- ("assistant-firecanyon-resolution" (25 "KEIRA" "GREAT! YOU HAVE THE 20 CELLS NEEDED TO POWER MY HEAT SHIELD!") @@ -865,9 +1230,106 @@ (1228 "KEIRA" "GOOD LUCK!") ) +("asstvb09" :hint #x24f + (0 "KEIRA" "YOU DON'T HAVE ENOUGH POWER CELLS TO FUEL MY HEAT SHIELD.") + (216) + (234 "KEIRA" "YOU CAN'T CROSS FIRE CANYON UNTIL YOU COLLECT ENOUGH POWER CELLS.") + ) + +("sksp0076" :hint #x501 + (0 "DAXTER" "JAK, HIT SOME JUMPS TO KEEP US OFF THE HOT GROUND!") + ) + +("sksp0077" :hint #x502 + (0 "DAXTER" "BALLOONS GOOD, BURNING HOT MAGMA BAD!") + ) + +("sksp0078" :hint #x503 + (0 "DAXTER" "RIDE ON THE RAISED PRECURSOR STUFF TO KEEP US COOL.") + ) + +("sksp0079" :hint #x504 + (0 "DAXTER" "AHH! I'M GONNA DIE! I'M A COMING, GRANDPAP!") + (257) + (270 "DAXTER" "YOU HAVE TO GET THIS THING TO THE OTHER SIDE BEFORE IT OVERHEATS!") + ) + +("sksp0080" :hint #x505 + (0 "DAXTER" "USE THE HOP-TURN TO STEER HARDER!") + ) + +("sksp0081" :hint #x506 + (0 "DAXTER" "AHH! MAYBE I SHOULD DRIVE!") + ) + +("sksp0082" :hint #x507 + (0 "DAXTER" "YOU ARE TRYING TO AVOID THOSE DARK ECO BOXES, RIGHT?") + ) + +("sksp0083" :hint #x508 + (0 "DAXTER" "OOH! SEE IF WE CAN CATCH AIR OFF THOSE LURKERS!") + ) + +("sksp0084" :hint #x509 + (0 "DAXTER" "TOO CLOSE, TOO CLOSE!") + ) + +("sksp0085" :hint #x50a + (0 "DAXTER" "WHOA! THIS BABY'S GETTING WAY TOO HOT!") + ) + +("sksp0086" :hint #x50b + (0 "DAXTER" "A FEW MORE SECONDS AND WE'RE FRIED FLUT FLUT!") + ) + +("sksp0087" :hint #x50d + (0 "DAXTER" "HIT THOSE BALLOONS TO COOL OFF!") + ) + +("sksp0088" :hint #x50e + (0 "DAXTER" "HERE COMES ANOTHER BALLOON!") + ) + +("sksp0089" :hint #x50f + (0 "DAXTER" "I WISH I SLEPT IN THIS MORNING!") + ) + +("sksp0090" :hint #x510 + (0 "DAXTER" "YOU MISSED A BALLOON! WE NEED THOSE OR WE'RE COOKED!") + ) + +("sksp0091" :hint #x511 + (0 "DAXTER" "THIS PUPPY'S GETTING TOO HOT!") + ) + +("sksp0092" :hint #x512 + (0 "DAXTER" "DO YOU SMELL SOMETHING BURNING?!") + ) + +("sksp0093" :hint #x513 + (0 "DAXTER" "I THINK MY TAIL'S ON FIRE!") + ) + +("sksp0094" :hint #x514 + (0 "DAXTER" "SHE'S GONNA BLOW!") + ) + +("sksp0095" :hint #x515 + (0 "DAXTER" "WAHOO! WE MADE IT!") + ) + +("sksp0096" :hint #x516 + (0 "DAXTER" "THERE'S SCOUT FLIES OUT HERE, TOO!") + ) ;; ----------------- ;; swamp +;; ----------------- + +("billy-accept" + (5 "BILLY" "GOOD! THOSE RATS WILL BE BACK ANYTIME.") + (109 "BILLY" "SHOOT ALL THEM RATS, AND KEEP 'EM FROM EATING AT LEAST ONE OF THEM SNACKS.") + ) ("billy-introduction" (6 "BILLY" "HOWDY, FRIENDS! ENJOYIN' MY BEAUTIFUL SWAMP? I OWN THESE HERE PARTS. EVERYTHING THAT DOESN'T SINK INTO THE MUD, THAT IS! HA HA HA...") @@ -889,48 +1351,52 @@ (7 "BILLY" "WELL, IF YOU CHANGE YOUR MIND, YOU KNOW WHERE TO FIND ME! (LAUGHS)") ) -("billy-accept" - (5 "BILLY" "GOOD! THOSE RATS WILL BE BACK ANYTIME.") - (109 "BILLY" "SHOOT ALL THEM RATS, AND KEEP 'EM FROM EATING AT LEAST ONE OF THEM SNACKS.") +("billy-reminder-1" + (36 "BILLY" "AHH, Y'ALL BACK TO HELP STOP THEM RATS?") ) ("billy-resolution" (1 "BILLY" "WELL FRY MY HIDE! YOU SURE KNOW HOW TO SHOOT! THANKS A HEAP FOR THE HELP.") ) -("billy-reminder-1" - (36 "BILLY" "AHH, Y'ALL BACK TO HELP STOP THEM RATS?") - ) - ;; ----------------- ;; citadel +;; ----------------- - -("green-sagecage-introduction" - (130 "SAGE" "IT'S ABOUT TIME YOU TWO DECIDED TO SHOW UP!") - (207 "DAXTER" "NICE TO SEE YOU TOO! DO THEY HAVE YOU MOPPING THE FLOORS NOW?") - (343 "SAGE" "THERE'S NO TIME FOR JOKES, DAXTER. GOL AND MAIA KIDNAPPED US") - (477 "SAGE" "TO SAP OUR ENERGIES TO POWER THEIR ABOMINABLE MACHINE.") - (574) - (577 :offscreen "SAGE" "IT APPEARS THEY HAVE COMBINED THE FUNCTIONAL REMAINS OF A PRECURSOR ROBOT") - (710 :offscreen "SAGE" "WITH SCAVENGED ARTIFACTS FROM ACROSS THE LAND.") - (800) - (803 :offscreen "SAGE" "THEN THEY ADDED A FEW DIABOLICAL ADDITIONS OF THEIR OWN,") - (921 :offscreen "SAGE" "CREATING THE ONE THING CAPABLE OF OPENING THE DARK ECO SILOS.") - (1060 "SAGE" "IF YOU CAN FREE THE FOUR OF US, WE CAN USE OUR COMBINED POWERS") - (1180 "SAGE" "TO BREAK THE FORCE SHIELD SURROUNDING THE ROBOT, BEFORE THEY USE IT TO DESTROY THE WORLD.") +("BLU-AM01" :hint #x0 + (0 "BLUE SAGE" "A LITTLE BIT OF HELP WOULD BE GOOD!") ) -("redsage-resolution" - (58 "RED SAGE" "(CHUCKLES) YOU'VE FINALLY COME TO RESCUE ME.") - (190 "RED SAGE" "DO YOU KNOW HOW LONG I'VE BEEN IN HERE? WHAT TOOK YOU SO LONG? AND UH... (CHUCKLES)") - (378 "RED SAGE" "WHAT ARE YOUR NAMES?") - (429) - (432 "DAXTER" "I'M DAXTER! HE'S JAK, HE'S WITH ME.") - (540 :offscreen "RED SAGE" "GOOD JOB, DAXTER. YOU'RE A REAL HERO.") - (677) - (703 "RED SAGE" "YOU'VE GOT TO STOP GOL FROM LAUNCHING THE ROBOT.") - (837 "RED SAGE" "I'LL USE MY ECO POWER TO HELP OPEN THE SHIELD DOOR.") +("BLU-AM02" :hint #x0 + (0 "BLUE SAGE" "OH, THIS IS A FINE MESS.") + ) + +("BLU-AM03" :hint #x0 + (0 "BLUE SAGE" "HMM, QUITE AN ENIGMA WE'RE FACING.") + ) + +("RED-AM01" :hint #x0 + (0 "RED SAGE" "IS ANYBODY THERE?") + ) + +("RED-AM02" :hint #x0 + (0 "RED SAGE" "WELL...") + ) + +("RED-AM03" :hint #x0 + (0 "RED SAGE" "WHAT KEPT YOU?") + ) + +("YEL-AM01" :hint #x0 + (0 "RED SAGE" "TIME IS RUNNIN' OUT!") + ) + +("YEL-AM02" :hint #x0 + (0 "RED SAGE" "HEY! GET ME OUTTA HERE!") + ) + +("YEL-AM03" :hint #x0 + (0 "RED SAGE" "WHEN I GET OUTTA HERE, SOME SHOOTIN'S GONNA START!") ) ("bluesage-resolution" @@ -946,27 +1412,19 @@ (980) ) -("yellowsage-resolution" - (57 "YELLOW SAGE" "WHO WOULDA THOUGHT I'D LIVE TO SEE THE DAY") - (137 "YELLOW SAGE" "WHEN I NEEDED TO BE RESCUED BY A BOY AND HIS MUSKRAT!") - (271 "YELLOW SAGE" "(SIGHS) I'M GONNA GIVE GOL AND MAIA A LITTLE PAYBACK FOR THIS EMBARRASSMENT!") - (471) - (480 "YELLOW SAGE" "THEN WE'LL SEE ABOUT COOKING UP SOME MUSKRAT STEW...") - (605) - ) - -("green-sagecage-resolution" - (35 "SAGE" "GOOD WORK, BOYS! YOU'RE REAL HEROES NOW.") - (158 "SAGE" "I'LL COMBINE MY GREEN ECO POWER WITH THE OTHER THREE SAGES") - (280 "SAGE" "AND TOGETHER WE'LL OPEN THE SHIELD DOOR SURROUNDING THE PRECURSOR ROBOT.") - (405 "DAXTER" "YEAH YEAH THAT SOUNDS LIKE A GOOD START.") - (457 "DAXTER" "AND THEN AFTER YOU GUYS OPEN THAT SHIELD, WHAT ARE YOU GONNA DO ABOUT THE ROBOT?") - (576 "SAGE" "NOTHING, DAXTER. WE HAVE TO KEEP THE SHIELD OPEN.") - (705 "SAGE" "IT'S UP TO YOU TWO TO FIGURE OUT HOW TO DESTROY THE ROBOT.") - (823 "DAXTER" "OH, GREAT. I GET TO HELP THE GUY THAT TURNED ME INTO A FURBALL") - (983 "DAXTER" "DESTROY THE ONLY PERSON WHO CAN TURN ME BACK!") - (1111) - (1161 "SAGE" "FIRST, SAVE THE WORLD! THEN WE'LL TRY TO CONVINCE GOL TO HELP DAXTER.") +("green-sagecage-introduction" + (130 "SAGE" "IT'S ABOUT TIME YOU TWO DECIDED TO SHOW UP!") + (207 "DAXTER" "NICE TO SEE YOU TOO! DO THEY HAVE YOU MOPPING THE FLOORS NOW?") + (343 "SAGE" "THERE'S NO TIME FOR JOKES, DAXTER. GOL AND MAIA KIDNAPPED US") + (477 "SAGE" "TO SAP OUR ENERGIES TO POWER THEIR ABOMINABLE MACHINE.") + (574) + (577 :offscreen "SAGE" "IT APPEARS THEY HAVE COMBINED THE FUNCTIONAL REMAINS OF A PRECURSOR ROBOT") + (710 :offscreen "SAGE" "WITH SCAVENGED ARTIFACTS FROM ACROSS THE LAND.") + (800) + (803 :offscreen "SAGE" "THEN THEY ADDED A FEW DIABOLICAL ADDITIONS OF THEIR OWN,") + (921 :offscreen "SAGE" "CREATING THE ONE THING CAPABLE OF OPENING THE DARK ECO SILOS.") + (1060 "SAGE" "IF YOU CAN FREE THE FOUR OF US, WE CAN USE OUR COMBINED POWERS") + (1180 "SAGE" "TO BREAK THE FORCE SHIELD SURROUNDING THE ROBOT, BEFORE THEY USE IT TO DESTROY THE WORLD.") ) ("green-sagecage-outro-preboss" @@ -986,65 +1444,216 @@ (1605 "SAGE" "JAK! TAKE THE ELEVATOR UP AND STOP THAT ROBOT!") ) -("BLU-AM01" :hint 0 (0 "BLUE SAGE" "A LITTLE BIT OF HELP WOULD BE GOOD!")) -("BLU-AM02" :hint 0 (0 "BLUE SAGE" "OH, THIS IS A FINE MESS.")) -("BLU-AM03" :hint 0 (0 "BLUE SAGE" "HMM, QUITE AN ENIGMA WE'RE FACING.")) -("RED-AM01" :hint 0 (0 "RED SAGE" "IS ANYBODY THERE?")) -("RED-AM02" :hint 0 (0 "RED SAGE" "WELL...")) -("RED-AM03" :hint 0 (0 "RED SAGE" "WHAT KEPT YOU?")) -("YEL-AM01" :hint 0 (0 "RED SAGE" "TIME IS RUNNIN' OUT!")) -("YEL-AM02" :hint 0 (0 "RED SAGE" "HEY! GET ME OUTTA HERE!")) -("YEL-AM03" :hint 0 (0 "RED SAGE" "WHEN I GET OUTTA HERE, SOME SHOOTIN'S GONNA START!")) +("green-sagecage-resolution" + (35 "SAGE" "GOOD WORK, BOYS! YOU'RE REAL HEROES NOW.") + (158 "SAGE" "I'LL COMBINE MY GREEN ECO POWER WITH THE OTHER THREE SAGES") + (280 "SAGE" "AND TOGETHER WE'LL OPEN THE SHIELD DOOR SURROUNDING THE PRECURSOR ROBOT.") + (405 "DAXTER" "YEAH YEAH THAT SOUNDS LIKE A GOOD START.") + (457 "DAXTER" "AND THEN AFTER YOU GUYS OPEN THAT SHIELD, WHAT ARE YOU GONNA DO ABOUT THE ROBOT?") + (576 "SAGE" "NOTHING, DAXTER. WE HAVE TO KEEP THE SHIELD OPEN.") + (705 "SAGE" "IT'S UP TO YOU TWO TO FIGURE OUT HOW TO DESTROY THE ROBOT.") + (823 "DAXTER" "OH, GREAT. I GET TO HELP THE GUY THAT TURNED ME INTO A FURBALL") + (983 "DAXTER" "DESTROY THE ONLY PERSON WHO CAN TURN ME BACK!") + (1111) + (1161 "SAGE" "FIRST, SAVE THE WORLD! THEN WE'LL TRY TO CONVINCE GOL TO HELP DAXTER.") + ) -("sksp0381" :hint #x806 (0 "DAXTER" "BREAK THE GENERATOR TO FREE THE SAGE!")) -("sksp0382" :hint #x807 (0 "DAXTER" "JUST HANG ON TO THE EDGE!")) -("sksp0383" :hint #x808 (0 "DAXTER" "WE GOT COMPANY, JAK. LOOOTS OF LURKERS!")) -("sksp0384" :hint #x809 (0 "DAXTER" "DESTROY THOSE GENERATORS!")) -("sksp0385" :hint #x80a (0 "DAXTER" "PUSH THE BUTTON!")) -("sksp0386" :hint #x80b (0 "DAXTER" "THAT ROBOT DOESN'T SCARE ME!")) -("sksp0387" :hint #x80c (0 "DAXTER" "LET'S GO CLIMB UP THOSE PLATFORMS!")) -("sksp0388" :hint #x80d (0 "DAXTER" "DON'T MISS THE NEXT LAUNCHER!")) -("sksp0389" :hint #x80e (0 "DAXTER" "WATCH IT!")) -("sksp0390" :hint #x80f (0 "DAXTER" "WE HAVE TO RESCUE ALL THE SAGES!")) -("sksp0391" :hint #x810 (0 "DAXTER" "DO YOU THINK GOL WILL STILL CHANGE ME BACK?")) -("sksp0392" :hint #x811 (0 "DAXTER" "SO THIS IS GOL AND MAIA'S CITADEL. NICE AND COZY.")) -("sksp0393" :hint #x812 (0 "DAXTER" "LAND ON THE NEXT LAUNCHER!")) -("sksp0378" :hint #x813 (0 "DAXTER" "THE DOOR'S OPEN, LET'S GET GOING!")) +("redsage-resolution" + (58 "RED SAGE" "(CHUCKLES) YOU'VE FINALLY COME TO RESCUE ME.") + (190 "RED SAGE" "DO YOU KNOW HOW LONG I'VE BEEN IN HERE? WHAT TOOK YOU SO LONG? AND UH... (CHUCKLES)") + (378 "RED SAGE" "WHAT ARE YOUR NAMES?") + (429) + (432 "DAXTER" "I'M DAXTER! HE'S JAK, HE'S WITH ME.") + (540 :offscreen "RED SAGE" "GOOD JOB, DAXTER. YOU'RE A REAL HERO.") + (677) + (703 "RED SAGE" "YOU'VE GOT TO STOP GOL FROM LAUNCHING THE ROBOT.") + (837 "RED SAGE" "I'LL USE MY ECO POWER TO HELP OPEN THE SHIELD DOOR.") + ) +("sksp0378" :hint #x813 + (0 "DAXTER" "THE DOOR'S OPEN, LET'S GET GOING!") + ) + +("sksp0381" :hint #x806 + (0 "DAXTER" "BREAK THE GENERATOR TO FREE THE SAGE!") + ) + +("sksp0382" :hint #x807 + (0 "DAXTER" "JUST HANG ON TO THE EDGE!") + ) + +("sksp0383" :hint #x808 + (0 "DAXTER" "WE GOT COMPANY, JAK. LOOOTS OF LURKERS!") + ) + +("sksp0384" :hint #x809 + (0 "DAXTER" "DESTROY THOSE GENERATORS!") + ) + +("sksp0385" :hint #x80a + (0 "DAXTER" "PUSH THE BUTTON!") + ) + +("sksp0386" :hint #x80b + (0 "DAXTER" "THAT ROBOT DOESN'T SCARE ME!") + ) + +("sksp0387" :hint #x80c + (0 "DAXTER" "LET'S GO CLIMB UP THOSE PLATFORMS!") + ) + +("sksp0388" :hint #x80d + (0 "DAXTER" "DON'T MISS THE NEXT LAUNCHER!") + ) + +("sksp0389" :hint #x80e + (0 "DAXTER" "WATCH IT!") + ) + +("sksp0390" :hint #x80f + (0 "DAXTER" "WE HAVE TO RESCUE ALL THE SAGES!") + ) + +("sksp0391" :hint #x810 + (0 "DAXTER" "DO YOU THINK GOL WILL STILL CHANGE ME BACK?") + ) + +("sksp0392" :hint #x811 + (0 "DAXTER" "SO THIS IS GOL AND MAIA'S CITADEL. NICE AND COZY.") + ) + +("sksp0393" :hint #x812 + (0 "DAXTER" "LAND ON THE NEXT LAUNCHER!") + ) + +("yellowsage-resolution" + (57 "YELLOW SAGE" "WHO WOULDA THOUGHT I'D LIVE TO SEE THE DAY") + (137 "YELLOW SAGE" "WHEN I NEEDED TO BE RESCUED BY A BOY AND HIS MUSKRAT!") + (271 "YELLOW SAGE" "(SIGHS) I'M GONNA GIVE GOL AND MAIA A LITTLE PAYBACK FOR THIS EMBARRASSMENT!") + (471) + (480 "YELLOW SAGE" "THEN WE'LL SEE ABOUT COOKING UP SOME MUSKRAT STEW...") + (605) + ) ;; ----------------- ;; finalboss +;; ----------------- +("GOL-AM01" :hint #x0 + (0 "GOL" "YOU JUST WON'T GIVE UP, WILL YOU?") + ) -("GOL-AM01" :hint 0 (0 "GOL" "YOU JUST WON'T GIVE UP, WILL YOU?")) -("GOL-AM02" :hint 0 (0 "GOL" "SOON, THE DARK ECO WILL BE OURS!")) -("GOL-AM03" :hint 0 (0 "GOL" "FINISH THEM!")) -("GOL-AM04" :hint 0 (0 "GOL" "FIRE!")) -("GOL-AM05" :hint 0 (0 "GOL" "BLAST THEM!")) -("GOL-AM06" :hint 0 (0 "GOL" "WHAT? THEM AGAIN!")) -("GOL-AM07" :hint 0 (0 "GOL" "IT'S TOO LATE!")) -("GOL-AM08" :hint 0 (0 "GOL" "YOU ARE MINE!")) -("GOL-AM09" :hint 0 (0 "GOL" "I HAVE YOU NOW!")) -("GOL-AM10" :hint 0 (0 "GOL" "AH HA HA HA HA!")) -("GOL-AM11" :hint 0 (0 "GOL" "NO MERCY!")) -("GOL-AM12" :hint 0 (0 "GOL" "TAKE THAT!")) -("GOL-AM13" :hint 0 (0 "GOL" "NICE TRY!")) -("GOL-AM14" :hint 0 (0 "GOL" "STAND STILL!")) -("GOL-AM15" :hint 0 (0 "GOL" "PATHETIC!")) -("GOL-AM16" :hint 0 (0 "GOL" "NOOO!")) -("GOL-AM17" :hint 0 (0 "GOL" "AHHH!")) -("GOL-AM18" :hint 0 (0 "GOL" "WE'LL OPEN THE SILO ALL THE WAY AND DESTROY YOU TWO!")) -("GOL-AM19" :hint 0 (0 "GOL" "LIGHT ECO! IT DOES EXIST!")) -("GOL-AM20" :hint 0 (0 "GOL" "WE'RE NOT GONNA TAKE IT!")) -("MAI-AM01" :hint 0 (0 "MAIA" "FINISH THEM BOTH OFF ONCE AND FOR ALL!")) -("MAI-AM02" :hint 0 (0 "MAIA" "TAKE THEM OUT!")) -("MAI-AM03" :hint 0 (0 "MAIA" "HA HA HA!")) -("MAI-AM04" :hint 0 (0 "MAIA" "ECO WON'T PROTECT YOU NOW!")) -("MAI-AM05" :hint 0 (0 "MAIA" "DO SOMETHING!")) -("MAI-AM06" :hint 0 (0 "MAIA" "AGH!")) -("MAI-AM07" :hint 0 (0 "MAIA" "NOOOOO!")) -("MAI-AM08" :hint 0 (0 "MAIA" "WE'LL OPEN THE SILO ALL THE WAY AND DESTROY YOU TWO!")) -("MAI-AM09" :hint 0 (0 "MAIA" "THEY MUST NOT BE ALLOWED TO GET IT!")) +("GOL-AM02" :hint #x0 + (0 "GOL" "SOON, THE DARK ECO WILL BE OURS!") + ) + +("GOL-AM03" :hint #x0 + (0 "GOL" "FINISH THEM!") + ) + +("GOL-AM04" :hint #x0 + (0 "GOL" "FIRE!") + ) + +("GOL-AM05" :hint #x0 + (0 "GOL" "BLAST THEM!") + ) + +("GOL-AM06" :hint #x0 + (0 "GOL" "WHAT? THEM AGAIN!") + ) + +("GOL-AM07" :hint #x0 + (0 "GOL" "IT'S TOO LATE!") + ) + +("GOL-AM08" :hint #x0 + (0 "GOL" "YOU ARE MINE!") + ) + +("GOL-AM09" :hint #x0 + (0 "GOL" "I HAVE YOU NOW!") + ) + +("GOL-AM10" :hint #x0 + (0 "GOL" "AH HA HA HA HA!") + ) + +("GOL-AM11" :hint #x0 + (0 "GOL" "NO MERCY!") + ) + +("GOL-AM12" :hint #x0 + (0 "GOL" "TAKE THAT!") + ) + +("GOL-AM13" :hint #x0 + (0 "GOL" "NICE TRY!") + ) + +("GOL-AM14" :hint #x0 + (0 "GOL" "STAND STILL!") + ) + +("GOL-AM15" :hint #x0 + (0 "GOL" "PATHETIC!") + ) + +("GOL-AM16" :hint #x0 + (0 "GOL" "NOOO!") + ) + +("GOL-AM17" :hint #x0 + (0 "GOL" "AHHH!") + ) + +("GOL-AM18" :hint #x0 + (0 "GOL" "WE'LL OPEN THE SILO ALL THE WAY AND DESTROY YOU TWO!") + ) + +("GOL-AM19" :hint #x0 + (0 "GOL" "LIGHT ECO! IT DOES EXIST!") + ) + +("GOL-AM20" :hint #x0 + (0 "GOL" "WE'RE NOT GONNA TAKE IT!") + ) + +("MAI-AM01" :hint #x0 + (0 "MAIA" "FINISH THEM BOTH OFF ONCE AND FOR ALL!") + ) + +("MAI-AM02" :hint #x0 + (0 "MAIA" "TAKE THEM OUT!") + ) + +("MAI-AM03" :hint #x0 + (0 "MAIA" "HA HA HA!") + ) + +("MAI-AM04" :hint #x0 + (0 "MAIA" "ECO WON'T PROTECT YOU NOW!") + ) + +("MAI-AM05" :hint #x0 + (0 "MAIA" "DO SOMETHING!") + ) + +("MAI-AM06" :hint #x0 + (0 "MAIA" "AGH!") + ) + +("MAI-AM07" :hint #x0 + (0 "MAIA" "NOOOOO!") + ) + +("MAI-AM08" :hint #x0 + (0 "MAIA" "WE'LL OPEN THE SILO ALL THE WAY AND DESTROY YOU TWO!") + ) + +("MAI-AM09" :hint #x0 + (0 "MAIA" "THEY MUST NOT BE ALLOWED TO GET IT!") + ) ("finalbosscam-white-eco" (163 :offscreen "GOL" "LIGHT ECO! IT DOES EXIST!") @@ -1093,13 +1702,6 @@ (2302 "DAXTER" "WHOA! PUT IT ON ICE, BIG GUY!") ) -("green-sagecage-outro-beat-boss-need-cells" - (151 "SAGE" "HOLY YAKOW! WHAT COULD THAT BE?") - (247 "KEIRA" "WOW, IT'S AN ANCIENT PRECURSOR DOOR!") - (342 :offscreen "KEIRA" "IT LOOKS LIKE IT WILL ONLY OPEN IF WE FILL ALL 100 HOLES WITH POWER CELLS!") - (485 "DAXTER" "OH BOY... HERE WE GO AGAIN!") - ) - ("green-sagecage-outro-beat-boss-enough-cells" (151 "SAGE" "HOLY YAKOW! WHAT COULD THAT BE?") (247 "KEIRA" "WOW, IT'S AN ANCIENT PRECURSOR DOOR!") @@ -1107,6 +1709,13 @@ (485 "DAXTER" "UH, WE'RE HEROES, REMEMBER? WE HAVE 100 POWER CELLS!") ) +("green-sagecage-outro-beat-boss-need-cells" + (151 "SAGE" "HOLY YAKOW! WHAT COULD THAT BE?") + (247 "KEIRA" "WOW, IT'S AN ANCIENT PRECURSOR DOOR!") + (342 :offscreen "KEIRA" "IT LOOKS LIKE IT WILL ONLY OPEN IF WE FILL ALL 100 HOLES WITH POWER CELLS!") + (485 "DAXTER" "OH BOY... HERE WE GO AGAIN!") + ) + ("green-sagecage-outro-big-finish" (322 "DAXTER" "WOW! WHAT IS IT?") (425) @@ -1116,89 +1725,143 @@ (602) ) - ;; ----------------- ;; training - +;; ----------------- ("asstvb40" :hint #x900 (0 "KEIRA" "THIS DEVICE IS A COMMUNICATOR. WITH IT, MY FATHER AND I") (181 "KEIRA" "CAN GIVE YOU ADVICE AT ANY TIME DURING YOUR QUEST.") ) + ("asstvb41" :hint #x901 (0 "KEIRA" "THESE FLOATING EGG-SHAPED THINGS ARE PRECURSOR ORBS.") (176) (180 "KEIRA" "COLLECT ENOUGH OF THEM, AND SOME OF THE VILLAGERS WILL GIVE YOU") (315 "KEIRA" "A POWER CELL IN EXCHANGE.") ) + ("asstvb42" :hint #x902 (0 "KEIRA" "THIS IS A POWER CELL, THE MOST IMPORTANT PRECURSOR ARTIFACT YOU CAN FIND!") (327) (333 "KEIRA" "YOU NEED TO COLLECT 20 OF THESE SO I CAN POWER THE HEAT SHIELD") (507 "KEIRA" "FOR YOUR A-GRAV ZOOMER.") ) + ("asstvb44" :hint #x903 (0 "KEIRA" "HEY, YOU FOUND ONE OF MY SCOUT FLIES!") (160 "KEIRA" "I SENT 7 OF THEM TO EACH AREA TO LOOK FOR POWER CELLS") (343 "KEIRA" "BUT THE LURKERS MUST HAVE CAPTURED THEM ALL!") ) + ("asstvb45" :hint #x904 (0 "KEIRA" "WOW! THAT LAST SCOUT FLY HAD A POWER CELL!") (219 "KEIRA" "I'LL BET IF YOU COLLECT ALL 7 IN EACH AREA YOU CAN FIND EVEN MORE POWER CELLS.") ) + ("asstvb46" :hint #x905 (0 "KEIRA" "BE CAREFUL IN THE OCEAN, THE WATERS ARE CHOCK-FULL OF LURKER SHARKS") (258 "KEIRA" "AND I'VE NEVER SEEN ANYONE DEFEAT ONE.") (419) (426 "KEIRA" "STAY CLEAR OF THEM IF YOU KNOW WHAT'S GOOD FOR YOU!") ) -("asstvb47" :hint #x906 (0 "KEIRA" "REMEMBER, THAT'S A POWER CELL. YOU NEED TO COLLECT AS MANY OF THOSE AS YOU CAN.")) + +("asstvb47" :hint #x906 + (0 "KEIRA" "REMEMBER, THAT'S A POWER CELL. YOU NEED TO COLLECT AS MANY OF THOSE AS YOU CAN.") + ) + ("asstvb48" :hint #x907 (0 "KEIRA" "THAT'S A BLUE ECO VENT! MORE CONCENTRATED THAN THE FLOATING CLUSTERS,") (253 "KEIRA" "THIS VENT WILL GIVE YOU A FULL CHARGE OF BLUE ECO,") (419 "KEIRA" "LETTING YOU USE IT FOR THE MAXIMUM TIME.") ) + ("sagevb21" :hint #x908 (3 "SAGE" "THOSE LITTLE GREEN BALLS OF ENERGY ON THE GROUND ARE A TYPE OF ECO.") (280 "SAGE" "PICK UP 50 SMALL GREEN ECOES, OR ONE BIG GREEN ONE TO INCREASE YOUR HEALTH.") ) + ("sagevb22" :hint #x909 (0 "SAGE" "THAT'S BLUE ECO, WHICH CONTAINS THE ENERGY OF MOTION.") (291 "SAGE" "BLUE ECO ALLOWS YOU TO RUN FAST, BREAK BOXES, AND EVEN ACTIVATE SOME PRECURSOR") (640 "SAGE" "ARTIFACTS WHEN YOU GET NEAR THEM.") ) -("sagevb23" :hint #x90a (0 "SAGE" "NOTICE HOW EACH BLUE ECO CLUSTER YOU PICK UP INCREASES THE TIME YOU CAN USE ITS POWER.")) + +("sagevb23" :hint #x90a + (0 "SAGE" "NOTICE HOW EACH BLUE ECO CLUSTER YOU PICK UP INCREASES THE TIME YOU CAN USE ITS POWER.") + ) + ("sagevb24" :hint #x90b (3 "SAGE" "THIS IS A PRECURSOR DOOR. IT CAN ONLY BE OPENED BY APPROACHING THE DOOR") (357 "SAGE" "WHILE CHANNELING BLUE ECO THROUGH YOUR BODY.") ) + ("sagevb25" :hint #x90c (3 "SAGE" "GOOD WORK, THE BLUE ECO CAUSED THE DOOR TO OPEN. WITH BLUE ECO, YOU CAN BREATHE") (365 "SAGE" "ENERGY INTO ALL KINDS OF PRECURSOR ARTIFACTS THAT HAVE LAIN DORMANT FOR YEARS.") ) -("sagevb26" :hint #x90d (0 "SAGE" "USE YOUR GOGGLES TO VIEW YOUR PROGRESS DURING THE ADVENTURE.")) -("sagevb27" :hint #x90e (1 "SAGE" "YOU CAN JUMP ONCE, THEN JUMP AGAIN IN THE AIR TO REACH EVEN HIGHER LEDGES.")) -("sagevb28" :hint #x90f (0 "SAGE" "WHEN YOU'RE SURROUNDED BY ENEMIES, USE YOUR SPIN KICK.")) -("sagevb29" :hint #x910 (0 "SAGE" "THAT'S NOT A SPIN KICK! TRY AGAIN.")) -("sagevb30" :hint #x911 (0 "SAGE" "COME ON, JAK! DO A SPIN KICK.")) -("sagevb31" :hint #x912 (0 "SAGE" "GOOD WORK. USE THE SPIN KICK WHEN YOU WANT A GOOD ALL-ROUND ATTACK.")) -("sagevb32" :hint #x913 (0 "SAGE" "FOR A STRAIGHT LINE, MOVING ATTACK, USE YOUR FIST TO PUNCH.")) -("sagevb33" :hint #x914 (0 "SAGE" "THAT'S NOT A PUNCH ATTACK! LET'S SEE YOU PUNCH ALREADY.")) -("sagevb34" :hint #x915 (0 "SAGE" "JAK! USE A PUNCH.")) -("sagevb35" :hint #x916 (0 "SAGE" "GREAT JOB. USE THE PUNCH TO CLOSE DISTANCE QUICKLY IN A TOUGH SPOT.")) + +("sagevb26" :hint #x90d + (0 "SAGE" "USE YOUR GOGGLES TO VIEW YOUR PROGRESS DURING THE ADVENTURE.") + ) + +("sagevb27" :hint #x90e + (1 "SAGE" "YOU CAN JUMP ONCE, THEN JUMP AGAIN IN THE AIR TO REACH EVEN HIGHER LEDGES.") + ) + +("sagevb28" :hint #x90f + (0 "SAGE" "WHEN YOU'RE SURROUNDED BY ENEMIES, USE YOUR SPIN KICK.") + ) + +("sagevb29" :hint #x910 + (0 "SAGE" "THAT'S NOT A SPIN KICK! TRY AGAIN.") + ) + +("sagevb30" :hint #x911 + (0 "SAGE" "COME ON, JAK! DO A SPIN KICK.") + ) + +("sagevb31" :hint #x912 + (0 "SAGE" "GOOD WORK. USE THE SPIN KICK WHEN YOU WANT A GOOD ALL-ROUND ATTACK.") + ) + +("sagevb32" :hint #x913 + (0 "SAGE" "FOR A STRAIGHT LINE, MOVING ATTACK, USE YOUR FIST TO PUNCH.") + ) + +("sagevb33" :hint #x914 + (0 "SAGE" "THAT'S NOT A PUNCH ATTACK! LET'S SEE YOU PUNCH ALREADY.") + ) + +("sagevb34" :hint #x915 + (0 "SAGE" "JAK! USE A PUNCH.") + ) + +("sagevb35" :hint #x916 + (0 "SAGE" "GREAT JOB. USE THE PUNCH TO CLOSE DISTANCE QUICKLY IN A TOUGH SPOT.") + ) + ("sagevb36" :hint #x917 (0 "SAGE" "SOMETIMES YOU'LL WANT TO HIT THINGS WITH A GREATER FORCE.") (218 "SAGE" "TO BREAK ONE OF THESE BOXES, YOU SHOULD JUMP IN THE AIR") (420 "SAGE" "AND THEN DIVE DOWN ONTO IT, HANDS FIRST.") ) -("sagevb37" :hint #x918 (0 "SAGE" "WHEN YOU GET SOME CONFIDENCE, TRY USING SOME OF YOUR MOVES IN COMBINATIONS.")) + +("sagevb37" :hint #x918 + (0 "SAGE" "WHEN YOU GET SOME CONFIDENCE, TRY USING SOME OF YOUR MOVES IN COMBINATIONS.") + ) + ("sagevb38" :hint #x919 (0 "SAGE" "YOU CAN'T COME BACK THROUGH THE WARP GATE UNTIL YOU FIND") (168 "SAGE" "ALL 4 POWER CELLS ON THIS ISLAND.") ) + ("sagevb39" :hint #x91a (0 "SAGE" "GREAT! YOU FOUND ALL OF THE POWER CELLS ON THE ISLAND.") (269 "SAGE" "COME BACK TO THE WARP GATE SO I CAN BRING YOU BACK TO THE LAB. HURRY UP!") ) +;; ----------------- +;; uncategorized +;; ----------------- diff --git a/game/assets/jak1/subtitle/game_subtitle_es.gd b/game/assets/jak1/subtitle/game_subtitle_es.gd new file mode 100644 index 0000000000..711666ce86 --- /dev/null +++ b/game/assets/jak1/subtitle/game_subtitle_es.gd @@ -0,0 +1,58 @@ +(language-id 3) + +;; ----------------- +;; intro +;; ----------------- + +;; ----------------- +;; sidekick +;; ----------------- + +;; ----------------- +;; village1 +;; ----------------- + +;; ----------------- +;; oracle +;; ----------------- + +("oracle-left-eye-1" + (0 "ORÁCULO" "HABÉIS DEMOSTRADO SER HONESTOS. É AQUÍ UNA BATERÍA.") + ) + +;; ----------------- +;; beach +;; ----------------- + +;; ----------------- +;; jungle +;; ----------------- + +;; ----------------- +;; misty +;; ----------------- + +;; ----------------- +;; firecanyon +;; ----------------- + +;; ----------------- +;; swamp +;; ----------------- + +;; ----------------- +;; citadel +;; ----------------- + +;; ----------------- +;; finalboss +;; ----------------- + +;; ----------------- +;; training +;; ----------------- + +;; ----------------- +;; uncategorized +;; ----------------- + diff --git a/game/assets/jak1/subtitle/game_subtitle_es.gs b/game/assets/jak1/subtitle/game_subtitle_es.gs deleted file mode 100644 index 79196a3028..0000000000 --- a/game/assets/jak1/subtitle/game_subtitle_es.gs +++ /dev/null @@ -1,6 +0,0 @@ -(language-id 3) - -;; ----------------- -;; oracle - -("oracle-left-eye-1" (0 "ORÁCULO" "HABÉIS DEMOSTRADO SER HONESTOS. É AQUÍ UNA BATERÍA.")) diff --git a/game/assets/jak1/subtitle/subtitle-editor-db.json b/game/assets/jak1/subtitle/subtitle-editor-db.json new file mode 100644 index 0000000000..07ed5d7d08 --- /dev/null +++ b/game/assets/jak1/subtitle/subtitle-editor-db.json @@ -0,0 +1,643 @@ +{ + "assistant-firecanyon-resolution": { + "entity_type": "assistant-firecanyon", + "process_name": "assistant-firecanyon-1", + "continue_name": "firecanyon-start", + "move_to": [-27.0, 31.0, -183.0], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": ["(dm-lavabike-ready 0 (debug-menu-msg press))"] + }, + "assistant-introduction-blue-eco-switch": { + "entity_type": "assistant", + "process_name": "assistant-11", + "continue_name": "village1-hut", + "move_to": [], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + ] + }, + "assistant-reminder-1-blue-eco-switch": { + "entity_type": "assistant", + "process_name": "assistant-11", + "continue_name": "village1-hut", + "move_to": [], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(close-status! (-> __GET-PROCESS__ tasks) (task-status need-introduction))" + ] + }, + "assistant-introduction-race-bike": { + "entity_type": "assistant", + "process_name": "assistant-11", + "continue_name": "village1-hut", + "move_to": [], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": ["(dm-give-cell (game-task jungle-eggtop))"] + }, + "assistant-reminder-1-generic": { + "entity_type": "assistant", + "process_name": "assistant-11", + "continue_name": "village1-hut", + "move_to": [], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(dm-task-resolution 16 (debug-menu-msg press))", + "(dm-task-resolution 216 (debug-menu-msg press))" + ] + }, + "assistant-reminder-1-race-bike": { + "entity_type": "assistant", + "process_name": "assistant-11", + "continue_name": "village1-hut", + "move_to": [], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(dm-give-cell (game-task jungle-eggtop))", + "(close-status! (-> __GET-PROCESS__ tasks) (task-status need-introduction))" + ] + }, + "billy-accept": { + "entity_type": "billy", + "process_name": "billy-2", + "continue_name": "swamp-game", + "move_to": [606.1, 0.45, -2024.81], + "execute_code": "(close-status! (-> __GET-PROCESS__ tasks) (task-status need-introduction))", + "requirements": [] + }, + "billy-introduction": { + "entity_type": "billy", + "process_name": "billy-2", + "continue_name": "swamp-game", + "move_to": [606.1, 0.45, -2024.81], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [] + }, + "billy-reject": { + "entity_type": "billy", + "process_name": "billy-2", + "continue_name": "swamp-game", + "move_to": [606.1, 0.45, -2024.81], + "execute_code": "(close-status! (-> __GET-PROCESS__ tasks) (task-status need-introduction))", + "requirements": [] + }, + "billy-reminder-1": { + "entity_type": "billy", + "process_name": "billy-2", + "continue_name": "swamp-game", + "move_to": [606.1, 0.45, -2024.81], + "execute_code": "(close-status! (-> __GET-PROCESS__ tasks) (task-status need-introduction))", + "requirements": [] + }, + "billy-resolution": { + "entity_type": "billy", + "process_name": "billy-2", + "continue_name": "swamp-game", + "move_to": [606.1, 0.45, -2024.81], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": ["(dm-task-reminder 288 (debug-menu-msg press))"] + }, + "bird-lady-beach-resolution": { + "entity_type": "bird-lady-beach", + "process_name": "bird-lady-beach-1", + "continue_name": "beach-start", + "move_to": [-76.90, 22.09, -270.7], + "move_first": true, + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(run-now-in-process (process-by-name \"flutflutegg-1\" *active-pool*) (lambda () (go flutflutegg-physics-fall)))" + ] + }, + "bird-lady-introduction": { + "entity_type": "bird-lady", + "process_name": "bird-lady-4", + "continue_name": "village1-hut", + "move_to": [-51, 10, -7], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [] + }, + "bird-lady-reminder-1": { + "entity_type": "bird-lady", + "process_name": "bird-lady-4", + "continue_name": "village1-hut", + "move_to": [-51, 10, -7], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(close-status! (-> __GET-PROCESS__ tasks) (task-status need-introduction))" + ] + }, + "bird-lady-reminder-2": { + "entity_type": "bird-lady", + "process_name": "bird-lady-4", + "continue_name": "village1-hut", + "move_to": [-51, 10, -7], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(close-status! (-> __GET-PROCESS__ tasks) (task-status need-introduction))", + "(save-reminder (-> __GET-PROCESS__ tasks) 1 0)" + ] + }, + "lrocklrg-falling": { + "entity_type": "beach-rock", + "process_name": "lrocklrg-1", + "continue_name": "beach-start", + "move_to": [-244, 32, -332], + "execute_code": "(send-event __GET-PROCESS__ 'trigger)", + "requirements": [] + }, + "bluesage-resolution": { + "entity_type": "blue-sagecage", + "process_name": "blue-sagecage-1", + "continue_name": "citadel-plat-end", + "move_to": [], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(close-specific-task! (game-task citadel-sage-blue) (task-status need-hint))", + "(close-specific-task! (game-task citadel-sage-red) (task-status need-hint))", + "(close-specific-task! (game-task citadel-sage-green) (task-status need-hint))", + "(close-specific-task! (game-task citadel-sage-yellow) (task-status need-hint))" + ] + }, + "explorer-introduction": { + "entity_type": "explorer", + "process_name": "explorer-4", + "continue_name": "village1-hut", + "move_to": [-87.04, 9.39, 43.64], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [] + }, + "explorer-reminder-1": { + "entity_type": "explorer", + "process_name": "explorer-4", + "continue_name": "village1-hut", + "move_to": [-87.04, 9.39, 43.64], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(close-status! (-> __GET-PROCESS__ tasks) (task-status need-introduction))" + ] + }, + "explorer-reminder-2": { + "entity_type": "explorer", + "process_name": "explorer-4", + "continue_name": "village1-hut", + "move_to": [-87.04, 9.39, 43.64], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(close-status! (-> __GET-PROCESS__ tasks) (task-status need-introduction))", + "(save-reminder (-> __GET-PROCESS__ tasks) 1 0)" + ] + }, + "explorer-resolution": { + "entity_type": "explorer", + "process_name": "explorer-4", + "continue_name": "village1-hut", + "move_to": [-87.04, 9.39, 43.64], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(close-status! (-> __GET-PROCESS__ tasks) (task-status need-introduction))", + "(dm-task-get-money 0 (debug-menu-msg press))" + ] + }, + "farmer-introduction": { + "entity_type": "farmer", + "process_name": "farmer-3", + "continue_name": "village1-hut", + "move_to": [-3.16, 1.77, -63.1], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [] + }, + "farmer-reminder-1": { + "entity_type": "farmer", + "process_name": "farmer-3", + "continue_name": "village1-hut", + "move_to": [-3.16, 1.77, -63.1], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(close-status! (-> __GET-PROCESS__ tasks) (task-status need-introduction))" + ] + }, + "farmer-reminder-2": { + "entity_type": "farmer", + "process_name": "farmer-3", + "continue_name": "village1-hut", + "move_to": [-3.16, 1.77, -63.1], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(close-status! (-> __GET-PROCESS__ tasks) (task-status need-introduction))", + "(save-reminder (-> __GET-PROCESS__ tasks) 1 0)" + ] + }, + "farmer-resolution": { + "entity_type": "farmer", + "process_name": "farmer-3", + "continue_name": "village1-hut", + "move_to": [-3.16, 1.77, -63.1], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": ["(dm-task-reminder 80 (debug-menu-msg press))"] + }, + "fisher-accept": { + "entity_type": "fisher", + "process_name": "fisher-1", + "continue_name": "jungle-start", + "move_to": [262.35, 2.28, -231.83], + "execute_code": "(close-status! (-> __GET-PROCESS__ tasks) (task-status need-introduction))", + "requirements": [] + }, + "fisher-introduction": { + "entity_type": "fisher", + "process_name": "fisher-1", + "continue_name": "jungle-start", + "move_to": [262.35, 2.28, -231.83], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [] + }, + "fisher-reject": { + "entity_type": "fisher", + "process_name": "fisher-1", + "continue_name": "jungle-start", + "move_to": [262.35, 2.28, -231.83], + "execute_code": "(close-status! (-> __GET-PROCESS__ tasks) (task-status need-introduction))", + "requirements": [] + }, + "fisher-reminder-1": { + "entity_type": "fisher", + "process_name": "fisher-1", + "continue_name": "jungle-start", + "move_to": [262.35, 2.28, -231.83], + "execute_code": "(close-status! (-> __GET-PROCESS__ tasks) (task-status need-introduction))", + "requirements": [] + }, + "fisher-resolution": { + "entity_type": "fisher", + "process_name": "fisher-1", + "continue_name": "jungle-start", + "move_to": [262.35, 2.28, -231.83], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": ["(dm-task-reminder 40 (debug-menu-msg press))"] + }, + "geologist-introduction": { + "entity_type": "geologist", + "process_name": "geologist-1", + "continue_name": "village2-start", + "move_to": [-56.6, 11.0, 32.9], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [] + }, + "green-sagecage-introduction": { + "entity_type": "green-sagecage", + "process_name": "green-sagecage-1", + "continue_name": "citadel-start", + "move_to": [262.35, 2.28, -231.83], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [] + }, + "green-sagecage-resolution": { + "entity_type": "green-sagecage", + "process_name": "green-sagecage-1", + "continue_name": "citadel-elevator", + "move_to": [262.35, 2.28, -231.83], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(close-specific-task! (game-task citadel-sage-blue) (task-status need-hint))", + "(close-specific-task! (game-task citadel-sage-red) (task-status need-hint))", + "(close-specific-task! (game-task citadel-sage-green) (task-status need-hint))", + "(close-specific-task! (game-task citadel-sage-yellow) (task-status need-hint))", + "(dm-give-all-cells 0 (debug-menu-msg press))" + ] + }, + "mayor-introduction": { + "entity_type": "mayor", + "process_name": "mayor-5", + "continue_name": "village1-hut", + "move_to": [-87.04, 9.39, 43.64], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [] + }, + "mayor-reminder-beams": { + "entity_type": "mayor", + "process_name": "mayor-5", + "continue_name": "village1-hut", + "move_to": [-87.04, 9.39, 43.64], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(dm-task-introduction 24 (debug-menu-msg press))", + "(dm-task-introduction 88 (debug-menu-msg press))" + ] + }, + "mayor-reminder-donation": { + "entity_type": "mayor", + "process_name": "mayor-5", + "continue_name": "village1-hut", + "move_to": [-87.04, 9.39, 43.64], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(dm-task-introduction 24 (debug-menu-msg press))", + "(dm-task-introduction 88 (debug-menu-msg press))", + "(save-reminder (-> __GET-PROCESS__ tasks) 1 0)" + ] + }, + "mayor-resolution-beams": { + "entity_type": "mayor", + "process_name": "mayor-5", + "continue_name": "village1-hut", + "move_to": [-87.04, 9.39, 43.64], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(dm-task-introduction 24 (debug-menu-msg press))", + "(dm-task-introduction 88 (debug-menu-msg press))", + "(dm-task-reminder 24 (debug-menu-msg press))" + ] + }, + "mayor-resolution-donation": { + "entity_type": "mayor", + "process_name": "mayor-5", + "continue_name": "village1-hut", + "move_to": [-87.04, 9.39, 43.64], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(dm-task-introduction 24 (debug-menu-msg press))", + "(dm-task-introduction 88 (debug-menu-msg press))", + "(dm-task-get-money 0 (debug-menu-msg press))" + ] + }, + "oracle-intro-1": { + "entity_type": "oracle", + "process_name": "oracle-1", + "continue_name": "village1-hut", + "move_to": [82.8, 18.2, 14.3], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(dm-task-hint 104 (debug-menu-msg press))", + "(dm-task-hint 112 (debug-menu-msg press))" + ] + }, + "oracle-left-eye-1": { + "entity_type": "oracle", + "process_name": "oracle-1", + "continue_name": "village1-hut", + "move_to": [82.8, 18.2, 14.3], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(dm-task-hint 104 (debug-menu-msg press))", + "(dm-task-hint 112 (debug-menu-msg press))", + "(dm-task-get-money 0 (debug-menu-msg press))", + "(dm-task-get-money 0 (debug-menu-msg press))", + "(dm-give-cell (game-task village1-oracle-money1))" + ] + }, + "oracle-left-eye-2": { + "entity_type": "oracle", + "process_name": "oracle-2", + "continue_name": "village2-start", + "move_to": [238.48, 11.9, -1532.6], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(dm-task-hint 104 (debug-menu-msg press))", + "(dm-task-hint 112 (debug-menu-msg press))", + "(close-status! (-> __GET-PROCESS__ tasks) (task-status need-introduction))", + "(dm-task-get-money 0 (debug-menu-msg press))", + "(dm-task-get-money 0 (debug-menu-msg press))", + "(dm-give-cell (game-task village2-oracle-money1))" + ] + }, + "oracle-left-eye-3": { + "entity_type": "oracle", + "process_name": "oracle-3", + "continue_name": "village3-start", + "move_to": [1024.94, 40.07, -3507.05], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(dm-task-hint 104 (debug-menu-msg press))", + "(dm-task-hint 112 (debug-menu-msg press))", + "(close-status! (-> __GET-PROCESS__ tasks) (task-status need-introduction))", + "(dm-task-get-money 0 (debug-menu-msg press))", + "(dm-task-get-money 0 (debug-menu-msg press))", + "(dm-give-cell (game-task village3-oracle-money1))" + ] + }, + "oracle-reminder-1": { + "entity_type": "oracle", + "process_name": "oracle-1", + "continue_name": "village1-hut", + "move_to": [82.8, 18.2, 14.3], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(dm-task-hint 104 (debug-menu-msg press))", + "(dm-task-hint 112 (debug-menu-msg press))", + "(close-status! (-> __GET-PROCESS__ tasks) (task-status need-introduction))" + ] + }, + "oracle-right-eye-1": { + "entity_type": "oracle", + "process_name": "oracle-1", + "continue_name": "village1-hut", + "move_to": [82.8, 18.2, 14.3], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(dm-task-hint 104 (debug-menu-msg press))", + "(dm-task-hint 112 (debug-menu-msg press))", + "(close-status! (-> __GET-PROCESS__ tasks) (task-status need-introduction))", + "(dm-task-get-money 0 (debug-menu-msg press))", + "(dm-task-get-money 0 (debug-menu-msg press))" + ] + }, + "oracle-right-eye-2": { + "entity_type": "oracle", + "process_name": "oracle-2", + "continue_name": "village2-start", + "move_to": [238.48, 11.9, -1532.6], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(dm-task-hint 104 (debug-menu-msg press))", + "(dm-task-hint 112 (debug-menu-msg press))", + "(close-status! (-> __GET-PROCESS__ tasks) (task-status need-introduction))", + "(dm-task-get-money 0 (debug-menu-msg press))", + "(dm-task-get-money 0 (debug-menu-msg press))" + ] + }, + "oracle-right-eye-3": { + "entity_type": "oracle", + "process_name": "oracle-3", + "continue_name": "village3-start", + "move_to": [1024.94, 40.07, -3507.05], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(dm-task-hint 104 (debug-menu-msg press))", + "(dm-task-hint 112 (debug-menu-msg press))", + "(close-status! (-> __GET-PROCESS__ tasks) (task-status need-introduction))", + "(dm-task-get-money 0 (debug-menu-msg press))", + "(dm-task-get-money 0 (debug-menu-msg press))" + ] + }, + "redsage-resolution": { + "entity_type": "red-sagecage", + "process_name": "red-sagecage-1", + "continue_name": "citadel-generator-end", + "move_to": [], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(close-specific-task! (game-task citadel-sage-blue) (task-status need-hint))", + "(close-specific-task! (game-task citadel-sage-red) (task-status need-hint))", + "(close-specific-task! (game-task citadel-sage-green) (task-status need-hint))", + "(close-specific-task! (game-task citadel-sage-yellow) (task-status need-hint))" + ] + }, + "sage-intro-sequence-a": { + "entity_type": "", + "process_name": "", + "continue_name": "intro-start", + "move_to": [], + "execute_code": "", + "requirements": [] + }, + "sage-intro-sequence-d1": { + "entity_type": "sage", + "process_name": "sage-23", + "continue_name": "village1-hut", + "move_to": [], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [] + }, + "sage-intro-sequence-d2": { + "entity_type": "sage", + "process_name": "sage-23", + "continue_name": "village1-hut", + "move_to": [], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [] + }, + "sage-intro-sequence-e": { + "entity_type": "sage", + "process_name": "sage-23", + "continue_name": "village1-hut", + "move_to": [], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(dm-task-resolution 736 (debug-menu-msg press))", + "(dm-task-resolution 744 (debug-menu-msg press))", + "(dm-task-resolution 752 (debug-menu-msg press))", + "(dm-task-resolution 760 (debug-menu-msg press))", + "(dm-task-resolution 872 (debug-menu-msg press))" + ] + }, + "sage-introduction-misty-cannon": { + "entity_type": "sage", + "process_name": "sage-23", + "continue_name": "village1-hut", + "move_to": [], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(dm-task-resolution 736 (debug-menu-msg press))", + "(dm-task-resolution 744 (debug-menu-msg press))", + "(dm-task-resolution 752 (debug-menu-msg press))", + "(dm-task-resolution 760 (debug-menu-msg press))", + "(dm-task-resolution 872 (debug-menu-msg press))", + "(dm-task-introduction 120 (debug-menu-msg press))" + ] + }, + "sage-reminder-1-ecorocks": { + "entity_type": "sage", + "process_name": "sage-23", + "continue_name": "village1-hut", + "move_to": [], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(dm-task-resolution 736 (debug-menu-msg press))", + "(dm-task-resolution 744 (debug-menu-msg press))", + "(dm-task-resolution 752 (debug-menu-msg press))", + "(dm-task-resolution 760 (debug-menu-msg press))", + "(dm-task-resolution 872 (debug-menu-msg press))", + "(dm-task-introduction 120 (debug-menu-msg press))", + "(dm-task-introduction 208 (debug-menu-msg press))", + "(dm-task-reminder 208 (debug-menu-msg press))" + ] + }, + "sage-reminder-1-generic": { + "entity_type": "sage", + "process_name": "sage-23", + "continue_name": "village1-hut", + "move_to": [], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(dm-task-resolution 736 (debug-menu-msg press))", + "(dm-task-resolution 744 (debug-menu-msg press))", + "(dm-task-resolution 752 (debug-menu-msg press))", + "(dm-task-resolution 760 (debug-menu-msg press))", + "(dm-task-resolution 872 (debug-menu-msg press))", + "(dm-task-introduction 120 (debug-menu-msg press))", + "(dm-task-introduction 208 (debug-menu-msg press))", + "(dm-task-reminder 208 (debug-menu-msg press))", + "(dm-task-resolution 120 (debug-menu-msg press))" + ] + }, + "sage-reminder-1-misty-cannon": { + "entity_type": "sage", + "process_name": "sage-23", + "continue_name": "village1-hut", + "move_to": [], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(dm-task-resolution 736 (debug-menu-msg press))", + "(dm-task-resolution 744 (debug-menu-msg press))", + "(dm-task-resolution 752 (debug-menu-msg press))", + "(dm-task-resolution 760 (debug-menu-msg press))", + "(dm-task-resolution 872 (debug-menu-msg press))", + "(dm-task-resolution 120 (debug-menu-msg press))", + "(dm-task-introduction 208 (debug-menu-msg press))" + ] + }, + "sculptor-introduction": { + "entity_type": "sculptor", + "process_name": "sculptor-6", + "continue_name": "village1-hut", + "move_to": [-110.43, 9.75, -3.46], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [] + }, + "sculptor-reminder-1": { + "entity_type": "sculptor", + "process_name": "sculptor-6", + "continue_name": "village1-hut", + "move_to": [-110.43, 9.75, -3.46], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(close-status! (-> __GET-PROCESS__ tasks) (task-status need-introduction))" + ] + }, + "sculptor-resolution": { + "entity_type": "sculptor", + "process_name": "sculptor-6", + "continue_name": "village1-hut", + "move_to": [-110.43, 9.75, -3.46], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": ["(dm-task-reminder 184 (debug-menu-msg press))"] + }, + "sidekick-human-intro-sequence-b": { + "entity_type": "", + "process_name": "", + "continue_name": "intro-start", + "move_to": [], + "execute_code": "", + "requirements": [] + }, + "sidekick-human-intro-sequence-c": { + "entity_type": "", + "process_name": "", + "continue_name": "intro-start", + "move_to": [], + "execute_code": "", + "requirements": [] + }, + "yellowsage-resolution": { + "entity_type": "yellow-sagecage", + "process_name": "yellow-sagecage-1", + "continue_name": "citadel-launch-end", + "move_to": [], + "execute_code": "(send-event __GET-PROCESS__ 'play-anim)", + "requirements": [ + "(close-specific-task! (game-task citadel-sage-blue) (task-status need-hint))", + "(close-specific-task! (game-task citadel-sage-red) (task-status need-hint))", + "(close-specific-task! (game-task citadel-sage-green) (task-status need-hint))", + "(close-specific-task! (game-task citadel-sage-yellow) (task-status need-hint))" + ] + } +} diff --git a/game/assets/jak1/subtitle/subtitle-groups.json b/game/assets/jak1/subtitle/subtitle-groups.json new file mode 100644 index 0000000000..a36aa75876 --- /dev/null +++ b/game/assets/jak1/subtitle/subtitle-groups.json @@ -0,0 +1,362 @@ +{ + "_groups": [ + "intro", + "sidekick", + "village1", + "oracle", + "beach", + "jungle", + "misty", + "firecanyon", + "swamp", + "citadel", + "finalboss", + "training", + "uncategorized" + ], + "beach": [ + "CHI-LO01", + "CHI-LO02", + "CHI-AM01", + "CHI-AM02", + "CHI-AM03", + "CHI-AM04", + "CHI-AM05", + "CHI-AM06", + "CHI-AM07", + "CHI-AM08", + "SCU-AM01", + "SCU-AM02", + "SCU-AM03", + "SCU-AM04", + "SCU-AM05", + "SCU-AM06", + "SCU-LO01", + "BIR-AM01", + "BIR-AM02", + "BIR-AM03", + "BIR-AM04", + "BIR-AM05", + "BIR-AM06", + "BIR-AM07", + "BIR-AM08", + "BIR-AM09", + "BIR-AM10", + "BIR-AM11", + "BIR-AM12", + "BIR-AM13", + "BIR-LO01", + "BIR-LO02", + "BIR-LO03", + "sksp0019", + "sksp0026", + "sksp0030", + "sksp0034", + "sksp0020", + "sksp0022", + "sksp0023", + "sksp0024", + "sksp0025", + "sksp0027", + "sksp0029", + "sagevb01", + "sksp0443", + "mayor-introduction", + "mayor-reminder-beams", + "mayor-reminder-donation", + "mayor-resolution-beams", + "mayor-resolution-donation", + "sculptor-introduction", + "sculptor-reminder-1", + "sculptor-resolution", + "bird-lady-introduction", + "bird-lady-reminder-1", + "bird-lady-reminder-2", + "bird-lady-beach-resolution" + ], + "citadel": [ + "green-sagecage-introduction", + "redsage-resolution", + "bluesage-resolution", + "yellowsage-resolution", + "green-sagecage-resolution", + "green-sagecage-outro-preboss", + "BLU-AM01", + "BLU-AM02", + "BLU-AM03", + "RED-AM01", + "RED-AM02", + "RED-AM03", + "YEL-AM01", + "YEL-AM02", + "YEL-AM03", + "sksp0381", + "sksp0382", + "sksp0383", + "sksp0384", + "sksp0385", + "sksp0386", + "sksp0387", + "sksp0388", + "sksp0389", + "sksp0390", + "sksp0391", + "sksp0392", + "sksp0393", + "sksp0378" + ], + "finalboss": [ + "GOL-AM01", + "GOL-AM02", + "GOL-AM03", + "GOL-AM04", + "GOL-AM05", + "GOL-AM06", + "GOL-AM07", + "GOL-AM08", + "GOL-AM09", + "GOL-AM10", + "GOL-AM11", + "GOL-AM12", + "GOL-AM13", + "GOL-AM14", + "GOL-AM15", + "GOL-AM16", + "GOL-AM17", + "GOL-AM18", + "GOL-AM19", + "GOL-AM20", + "MAI-AM01", + "MAI-AM02", + "MAI-AM03", + "MAI-AM04", + "MAI-AM05", + "MAI-AM06", + "MAI-AM07", + "MAI-AM08", + "MAI-AM09", + "finalbosscam-white-eco", + "green-sagecage-daxter-sacrifice", + "green-sagecage-outro-beat-boss-a", + "green-sagecage-outro-beat-boss-b", + "green-sagecage-outro-beat-boss-need-cells", + "green-sagecage-outro-beat-boss-enough-cells", + "green-sagecage-outro-big-finish" + ], + "firecanyon": [ + "asstvb09", + "sksp0076", + "sksp0077", + "sksp0078", + "sksp0079", + "sksp0080", + "sksp0081", + "sksp0082", + "sksp0083", + "sksp0084", + "sksp0085", + "sksp0086", + "sksp0087", + "sksp0088", + "sksp0089", + "sksp0090", + "sksp0091", + "sksp0092", + "sksp0093", + "sksp0094", + "sksp0095", + "sksp0096", + "assistant-firecanyon-resolution" + ], + "intro": [ + "sage-intro-sequence-a", + "sidekick-human-intro-sequence-b", + "sidekick-human-intro-sequence-c", + "sage-intro-sequence-d1", + "sage-intro-sequence-d2", + "sage-intro-sequence-e" + ], + "jungle": [ + "FIS-AM01", + "FIS-AM02", + "FIS-AM03", + "FIS-AM04", + "FIS-AM05", + "FIS-AM06", + "FIS-LO01", + "FIS-LO03", + "FIS-LO04", + "FIS-LO05", + "FIS-TA01", + "FIS-TA02", + "FIS-TA1A", + "FIS-TA2A", + "FIS-TA03", + "FIS-TA04", + "FIS-TA05", + "FIS-TA06", + "FIS-TA07", + "FIS-TA08", + "FIS-TA09", + "FIS-TA10", + "FIS-TA11", + "sksp0038", + "sksp0040", + "sksp0041", + "sksp0011", + "sksp0039", + "sksp0018", + "sksp0037", + "sksp0b42", + "asstvb02", + "sksp0049", + "sksp0050", + "sksp0051", + "sksp0052", + "sksp0053", + "sksp0054", + "fisher-introduction", + "fisher-reminder-1", + "fisher-reject", + "fisher-accept", + "fisher-resolution" + ], + "misty": [ + "sksp0031", + "sksp0064", + "sksp0067", + "sksp0059", + "sksp0060", + "sksp0056", + "sksp0062", + "sksp0063", + "sagevb02", + "asstvb03", + "sksp0069", + "sksp0070", + "sksp0435" + ], + "oracle": [ + "oracle-intro-1", + "oracle-reminder-1", + "oracle-right-eye-1", + "oracle-left-eye-1", + "oracle-right-eye-2", + "oracle-left-eye-2", + "oracle-right-eye-3", + "oracle-left-eye-3" + ], + "sidekick": [ + "sksp0014", + "sksp0009", + "sksp0035", + "sksp0001", + "sksp0002", + "sksp0003", + "sksp0004", + "sksp0005", + "sksp0006", + "sksp0007", + "sksp0008", + "sksp009a", + "sksp009b", + "sksp009c", + "sksp009d", + "sksp009e", + "sksp009f", + "sksp009g", + "sksp009i", + "sksp009j", + "sksp009k", + "sksp0071", + "sksp0072", + "sksp0073", + "sksp0145" + ], + "swamp": [ + "billy-introduction", + "billy-reject", + "billy-accept", + "billy-resolution", + "billy-reminder-1" + ], + "training": [ + "asstvb40", + "asstvb41", + "asstvb42", + "asstvb44", + "asstvb45", + "asstvb46", + "asstvb47", + "asstvb48", + "sagevb21", + "sagevb22", + "sagevb23", + "sagevb24", + "sagevb25", + "sagevb26", + "sagevb27", + "sagevb28", + "sagevb29", + "sagevb30", + "sagevb31", + "sagevb32", + "sagevb33", + "sagevb34", + "sagevb35", + "sagevb36", + "sagevb37", + "sagevb38", + "sagevb39" + ], + "uncategorized": [], + "village1": [ + "SAGELP03", + "SAGELP04", + "SAGELP05", + "SAGELP06", + "SAGELP11", + "ASSTLP01", + "ASSTLP02", + "ASSTLP03", + "ASSTLP04", + "ASSTLP05", + "EXP-AM01", + "EXP-AM02", + "EXP-AM03", + "EXP-AM04", + "EXP-AM05", + "EXP-LO02", + "FAR-AM01", + "FAR-AM02", + "FAR-AM2A", + "FAR-LO01", + "FAR-LO1A", + "sksp0010", + "sksp0013", + "sksp0015", + "sksp0017", + "sksp0043", + "sksp018a", + "asstvb04", + "asstvb08", + "sage-introduction-misty-cannon", + "sage-reminder-1-misty-cannon", + "sage-reminder-1-ecorocks", + "sage-reminder-1-generic", + "sage-reminder-2-generic", + "assistant-introduction-blue-eco-switch", + "assistant-reminder-1-blue-eco-switch", + "assistant-introduction-race-bike", + "assistant-reminder-1-race-bike", + "assistant-reminder-1-generic", + "farmer-introduction", + "farmer-reminder-1", + "farmer-reminder-2", + "farmer-resolution", + "explorer-introduction", + "explorer-reminder-1", + "explorer-reminder-2", + "explorer-resolution" + ] +} diff --git a/game/graphics/opengl_renderer/OpenGLRenderer.cpp b/game/graphics/opengl_renderer/OpenGLRenderer.cpp index d37b8f06e9..145ff8f072 100644 --- a/game/graphics/opengl_renderer/OpenGLRenderer.cpp +++ b/game/graphics/opengl_renderer/OpenGLRenderer.cpp @@ -362,6 +362,10 @@ void OpenGLRenderer::render(DmaFollower dma, const RenderOptions& settings) { m_small_profiler.draw(m_render_state.load_status_debug, stats); } + if (settings.draw_subtitle_editor_window) { + m_subtitle_editor.draw_window(); + } + if (settings.save_screenshot) { finish_screenshot(settings.screenshot_path, settings.window_width_px, settings.window_height_px, settings.lbox_width_px, settings.lbox_height_px); diff --git a/game/graphics/opengl_renderer/OpenGLRenderer.h b/game/graphics/opengl_renderer/OpenGLRenderer.h index 8ec1e2f817..6c01ab9950 100644 --- a/game/graphics/opengl_renderer/OpenGLRenderer.h +++ b/game/graphics/opengl_renderer/OpenGLRenderer.h @@ -9,6 +9,7 @@ #include "game/graphics/opengl_renderer/Profiler.h" #include "game/graphics/opengl_renderer/opengl_utils.h" #include "game/graphics/opengl_renderer/CollideMeshRenderer.h" +#include "game/tools/subtitles/subtitle_editor.h" struct RenderOptions { int window_height_px = 0; @@ -18,6 +19,7 @@ struct RenderOptions { bool draw_render_debug_window = false; bool draw_profiler_window = false; bool draw_small_profiler_window = false; + bool draw_subtitle_editor_window = false; bool save_screenshot = false; std::string screenshot_path; @@ -65,6 +67,7 @@ class OpenGLRenderer { SharedRenderState m_render_state; Profiler m_profiler; SmallProfiler m_small_profiler; + SubtitleEditor m_subtitle_editor; std::array, (int)BucketId::MAX_BUCKETS> m_bucket_renderers; std::array m_bucket_categories; diff --git a/game/graphics/opengl_renderer/debug_gui.cpp b/game/graphics/opengl_renderer/debug_gui.cpp index bf9129e52e..5e5b9ebfa1 100644 --- a/game/graphics/opengl_renderer/debug_gui.cpp +++ b/game/graphics/opengl_renderer/debug_gui.cpp @@ -97,6 +97,11 @@ void OpenGlDebugGui::draw(const DmaStats& dma_stats) { ImGui::EndMenu(); } + if (ImGui::BeginMenu("Tools")) { + ImGui::MenuItem("Subtitle Editor", nullptr, &m_subtitle_editor); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Screenshot")) { ImGui::MenuItem("Screenshot Next Frame!", nullptr, &m_want_screenshot); ImGui::InputText("File", m_screenshot_save_name, 50); diff --git a/game/graphics/opengl_renderer/debug_gui.h b/game/graphics/opengl_renderer/debug_gui.h index 0ff186d6b6..a9da24ec95 100644 --- a/game/graphics/opengl_renderer/debug_gui.h +++ b/game/graphics/opengl_renderer/debug_gui.h @@ -44,6 +44,7 @@ class OpenGlDebugGui { void draw(const DmaStats& dma_stats); bool should_draw_render_debug() const { return m_draw_debug; } bool should_draw_profiler() const { return m_draw_profiler; } + bool should_draw_subtitle_editor() const { return m_subtitle_editor; } const char* screenshot_name() const { return m_screenshot_save_name; } bool should_advance_frame() { return m_frame_timer.should_advance_frame(); } @@ -73,6 +74,7 @@ class OpenGlDebugGui { bool m_draw_frame_time = false; bool m_draw_profiler = false; bool m_draw_debug = false; + bool m_subtitle_editor = false; bool m_want_screenshot = false; char m_screenshot_save_name[256] = "screenshot.png"; bool m_vsync = true; diff --git a/game/graphics/pipelines/opengl.cpp b/game/graphics/pipelines/opengl.cpp index 705b712de6..f534e95d36 100644 --- a/game/graphics/pipelines/opengl.cpp +++ b/game/graphics/pipelines/opengl.cpp @@ -219,6 +219,11 @@ std::string make_output_file_name(const std::string& file_name) { } } // namespace +static bool endsWith(std::string_view str, std::string_view suffix) { + return str.size() >= suffix.size() && + 0 == str.compare(str.size() - suffix.size(), suffix.size(), suffix); +} + void render_game_frame(int width, int height, int lbox_width, int lbox_height) { // wait for a copied chain. bool got_chain = false; @@ -240,11 +245,17 @@ void render_game_frame(int width, int height, int lbox_width, int lbox_height) { options.lbox_width_px = lbox_width; options.draw_render_debug_window = g_gfx_data->debug_gui.should_draw_render_debug(); options.draw_profiler_window = g_gfx_data->debug_gui.should_draw_profiler(); + options.draw_subtitle_editor_window = g_gfx_data->debug_gui.should_draw_subtitle_editor(); options.save_screenshot = g_gfx_data->debug_gui.get_screenshot_flag(); options.draw_small_profiler_window = g_gfx_data->debug_gui.small_profiler; options.pmode_alp_register = g_gfx_data->pmode_alp; if (options.save_screenshot) { - options.screenshot_path = make_output_file_name(g_gfx_data->debug_gui.screenshot_name()); + // ensure the screenshot has an extension + std::string temp_path = g_gfx_data->debug_gui.screenshot_name(); + if (endsWith(temp_path, ".png")) { + temp_path += ".png"; + } + options.screenshot_path = make_output_file_name(temp_path); } if constexpr (run_dma_copy) { auto& chain = g_gfx_data->dma_copier.get_last_result(); diff --git a/game/system/newpad.cpp b/game/system/newpad.cpp index 8ff301ea16..813f2d4d92 100644 --- a/game/system/newpad.cpp +++ b/game/system/newpad.cpp @@ -197,6 +197,11 @@ void DefaultMapping(MappingInfo& mapping) { } } + // TODO - these are different from the analog bindings above and cause + // the keyboard to be bound to controls regardless + // + // Need someway to toggle off -- where do we have access to the game's settings? + // R1 / L1 MapButton(mapping, Button::L1, 0, GLFW_KEY_Q); MapButton(mapping, Button::R1, 0, GLFW_KEY_O); diff --git a/game/tools/subtitles/subtitle_editor.cpp b/game/tools/subtitles/subtitle_editor.cpp new file mode 100644 index 0000000000..7236e06b0f --- /dev/null +++ b/game/tools/subtitles/subtitle_editor.cpp @@ -0,0 +1,499 @@ +#include "subtitle_editor.h" + +#include "third-party/imgui/imgui.h" +#include "third-party/imgui/imgui_stdlib.h" +#include "third-party/fmt/core.h" +#include "common/util/FileUtil.h" +#include "common/util/json_util.h" +#include +#include +#include "common/deserialization/subtitles/subtitles.h" + +SubtitleEditor::SubtitleEditor() : m_repl(8181) { + std::string db_path = (file_util::get_jak_project_dir() / "game" / "assets" / "jak1" / + "subtitle" / "subtitle-editor-db.json") + .string(); + auto config_str = file_util::read_text_file(db_path); + auto db_data = parse_commented_json(config_str, db_path); + + for (const auto& [key, val] : db_data.items()) { + auto new_entry = SubtitleEditorDB::Entry(); + try { + new_entry.entity_type = val.at("entity_type").get(); + new_entry.process_name = val.at("process_name").get(); + new_entry.continue_name = val.at("continue_name").get(); + new_entry.move_to = val.at("move_to").get>(); + if (val.contains("move_first")) { + new_entry.move_first = val.at("move_first").get(); + } else { + new_entry.move_first = false; + } + if (new_entry.move_to.size() != 0 && new_entry.move_to.size() != 3) { + fmt::print("Bad subtitle db entry, provide 0 or 3 coordinates for 'move_to' - {}", key); + continue; + } + new_entry.execute_code = val.at("execute_code").get(); + new_entry.requirements = val.at("requirements").get>(); + m_db.emplace(key, new_entry); + } catch (std::exception& ex) { + fmt::print("Bad subtitle db entry - {} - {}", key, ex.what()); + } + } + + m_subtitle_db = load_subtitle_project(); + m_filter = m_filter_placeholder; + m_filter_hints = m_filter_placeholder; + m_repl.connect(); +} + +void SubtitleEditor::repl_set_continue_point(const std::string_view& continue_point) { + m_repl.eval( + fmt::format("(start 'play (get-continue-by-name *game-info* \"{}\"))", continue_point)); +} + +void SubtitleEditor::repl_move_jak(const double x, const double y, const double z) { + m_repl.eval( + fmt::format("(move-to-point! (-> *target* control) (new 'static 'vector :x (meters {:.1f}) " + ":y (meters {:.1f}) :z (meters {:.1f})))", + x, y, z)); + m_repl.eval("(send-event *camera* 'teleport)"); +} + +void SubtitleEditor::repl_reset_game() { + m_repl.eval("(set! (-> *game-info* mode) 'debug)"); + m_repl.eval("(initialize! *game-info* 'game (the-as game-save #f) (the-as string #f))"); +} + +std::string SubtitleEditor::repl_get_process_string(const std::string_view& entity_type, + const std::string_view& process_name) { + return fmt::format("(the-as {} (process-by-name \"{}\" *active-pool*))", entity_type, + process_name); +} + +void SubtitleEditor::repl_execute_cutscene_code(const SubtitleEditorDB::Entry& entry) { + // Reset the game first to get to a known state + repl_reset_game(); + + if (entry.move_first) { + // Set Jak's Continue Point + if (!entry.continue_name.empty()) { + repl_set_continue_point(entry.continue_name); + } + // Move Jak into position + if (!entry.move_to.empty()) { + repl_move_jak(entry.move_to[0], entry.move_to[1], entry.move_to[2]); + } + } + + // Run any requirements to setup the task state + if (!entry.requirements.empty()) { + // Replace __GET-PROCESS__ + for (const auto& form : entry.requirements) { + std::string temp = form; + temp = std::regex_replace(temp, std::regex("__GET-PROCESS__"), + repl_get_process_string(entry.entity_type, entry.process_name)); + m_repl.eval(temp); + } + } + + if (!entry.move_first) { + // Set Jak's Continue Point + if (!entry.continue_name.empty()) { + repl_set_continue_point(entry.continue_name); + } + // Move Jak into position + if (!entry.move_to.empty()) { + repl_move_jak(entry.move_to[0], entry.move_to[1], entry.move_to[2]); + } + } + + // Execute the critical code - typically this means sending a 'play-anim event to the + // process-taskable in question + if (!entry.execute_code.empty()) { + std::string temp = entry.execute_code; + temp = std::regex_replace(temp, std::regex("__GET-PROCESS__"), + repl_get_process_string(entry.entity_type, entry.process_name)); + m_repl.eval(temp); + } +} + +void SubtitleEditor::repl_rebuild_text() { + m_repl.eval("(make-text)"); + // NOTE - still no clue how this doesn't switch languages lol + m_repl.eval("(1+! (-> *subtitle-text* lang))"); +} + +bool SubtitleEditor::is_scene_in_current_lang(const std::string& scene_name) { + return m_subtitle_db.m_banks.at(m_current_language)->m_scenes.count(scene_name) > 0; +} + +void SubtitleEditor::draw_window() { + ImGui::Begin("Subtitle Editor"); + + if (ImGui::Button("Save Changes")) { + m_files_saved_successfully = std::make_optional(write_subtitle_db_to_files(m_subtitle_db)); + repl_rebuild_text(); + } + if (m_files_saved_successfully.has_value()) { + ImGui::SameLine(); + if (m_files_saved_successfully.value()) { + ImGui::PushStyleColor(ImGuiCol_Text, m_success_text_color); + ImGui::Text("Saved!"); + ImGui::PopStyleColor(); + } else { + ImGui::PushStyleColor(ImGuiCol_Text, m_success_text_color); + ImGui::Text("Error!"); + ImGui::PopStyleColor(); + } + } + + draw_edit_options(); + draw_repl_options(); + + draw_current_cutscene(); + + if (ImGui::TreeNode("All Cutscenes")) { + ImGui::InputText("New Scene Name", &m_new_scene_name); + // TODO - make this a dropdown + ImGui::InputText("New Scene Group", &m_new_scene_group); + ImGui::InputText("Filter", &m_filter, ImGuiInputTextFlags_::ImGuiInputTextFlags_AutoSelectAll); + if (is_scene_in_current_lang(m_new_scene_name)) { + ImGui::PushStyleColor(ImGuiCol_Text, m_error_text_color); + ImGui::Text("Scene already exists with that name, no!"); + ImGui::PopStyleColor(); + } + if (m_new_scene_group.empty()) { + ImGui::PushStyleColor(ImGuiCol_Text, m_error_text_color); + ImGui::Text("You must provide a group to sort the scene into!"); + ImGui::PopStyleColor(); + } + if (!is_scene_in_current_lang(m_new_scene_name) && !m_new_scene_group.empty()) { + if (ImGui::Button("Add Scene")) { + GameSubtitleSceneInfo newScene; + newScene.m_name = m_new_scene_name; + newScene.m_kind = SubtitleSceneKind::Movie; + newScene.m_id = 0; // TODO - id is always zero, bug in subtitles.cpp? + newScene.m_sorting_group = m_new_scene_group; + m_subtitle_db.m_banks.at(m_current_language)->add_scene(newScene); + m_new_scene_name = ""; + } + } + + draw_all_cutscene_groups(); + ImGui::TreePop(); + } + + // TODO - hints + + ImGui::End(); +} + +void SubtitleEditor::draw_edit_options() { + if (ImGui::TreeNode("Editing Options")) { + ImGui::InputInt("Editing language ID", &m_current_language); + ImGui::InputInt("Base language ID", &m_base_language); + ImGui::Checkbox("Show missing cutscenes from base", &m_base_show_missing_cutscenes); + ImGui::InputText("New Subtitle Group Name", &m_new_scene_group_name); + if (!m_new_scene_group_name.empty()) { + if (m_new_scene_group_name == "_groups" || + std::find(m_subtitle_db.m_subtitle_groups->m_group_order.begin(), + m_subtitle_db.m_subtitle_groups->m_group_order.end(), m_new_scene_group_name) != + m_subtitle_db.m_subtitle_groups->m_group_order.end()) { + ImGui::PushStyleColor(ImGuiCol_Text, m_error_text_color); + ImGui::Text("Invalid group name, has to be unique and not '_groups'"); + ImGui::PopStyleColor(); + } else if (ImGui::Button("Add New Group")) { + m_subtitle_db.m_subtitle_groups->m_group_order.push_back(m_new_scene_group_name); + m_new_scene_group_name = ""; + } + } + ImGui::TreePop(); + } +} + +void SubtitleEditor::draw_repl_options() { + if (ImGui::TreeNode("REPL Options")) { + // TODO - the ReplServer should eventually be able to return statuses to make this easier: + // - Has the game been built before? + // - Is the repl connected? + ImGui::TextWrapped( + "This tool requires a REPL connected to the game, with the game built. Run the following " + "to do so:"); + ImGui::Text(" - `task repl`"); + ImGui::Text(" - `(lt)`"); + ImGui::Text(" - `(mi)`"); + ImGui::Text(" - Click Connect Below!"); + if (m_repl.is_connected()) { + ImGui::PushStyleColor(ImGuiCol_Text, m_success_text_color); + ImGui::Text("REPL Connected, should be good to go!"); + ImGui::PopStyleColor(); + } else { + if (ImGui::Button("Connect to REPL")) { + m_repl.connect(); + if (!m_repl.is_connected()) { + ImGui::PushStyleColor(ImGuiCol_Text, m_error_text_color); + ImGui::Text("Could not connect."); + ImGui::PopStyleColor(); + } + } + } + ImGui::TreePop(); + } +} + +void SubtitleEditor::draw_all_cutscene_groups() { + for (auto& group_name : m_subtitle_db.m_subtitle_groups->m_group_order) { + ImGui::SetNextItemOpen(true); + if (ImGui::TreeNode(group_name.c_str())) { + draw_all_scenes(group_name, false); + draw_all_scenes(group_name, true); + ImGui::TreePop(); + } + } +} + +void SubtitleEditor::draw_all_scenes(std::string group_name, bool base_cutscenes) { + auto& scenes = + m_subtitle_db.m_banks.at(base_cutscenes ? m_base_language : m_current_language)->m_scenes; + auto scenes_in_group = m_subtitle_db.m_subtitle_groups->m_groups[group_name]; + for (auto& scene_name : scenes_in_group) { + if (scenes.count(scene_name) == 0) { + continue; + } + auto& scene_info = scenes[scene_name]; + // Don't duplicate entries + if (base_cutscenes && is_scene_in_current_lang(scene_name)) { + continue; + } + bool is_current_scene = m_current_scene && m_current_scene->m_name == scene_info.m_name; + if (scene_info.m_kind != SubtitleSceneKind::Movie) { + continue; + } + if ((!m_filter.empty() && m_filter != m_filter_placeholder) && + scene_name.find(m_filter) == std::string::npos) { + continue; + } + if (!base_cutscenes && is_current_scene) { + ImGui::PushStyleColor(ImGuiCol_Text, m_selected_text_color); + } + if (base_cutscenes) { + ImGui::PushStyleColor(ImGuiCol_Text, m_disabled_text_color); + } + if (ImGui::TreeNode( + fmt::format("{}-{}", scene_name, base_cutscenes ? m_base_language : m_current_language) + .c_str(), + scene_name.c_str())) { + if (base_cutscenes || is_current_scene) { + ImGui::PopStyleColor(); + } + if (!base_cutscenes && !is_current_scene) { + if (ImGui::Button("Select as Current")) { + m_current_scene = &scene_info; + } + } + if (base_cutscenes) { + if (ImGui::Button("Copy from Base Language")) { + m_subtitle_db.m_banks.at(m_current_language)->add_scene(scene_info); + } + } + if (!m_repl.is_connected()) { + ImGui::PushStyleColor(ImGuiCol_Text, m_error_text_color); + ImGui::Text("REPL not connected, can't play!"); + ImGui::PopStyleColor(); + } else if (m_db.count(scene_info.m_name) > 0) { + if (ImGui::Button("Play Scene")) { + if (m_db.count(scene_info.m_name) == 1) { + repl_execute_cutscene_code(m_db[scene_info.m_name]); + } + } + ImGui::SameLine(); + ImGui::PushStyleColor(ImGuiCol_Text, m_disabled_text_color); + ImGui::TextWrapped("You may have to click twice, load times cause issues"); + ImGui::PopStyleColor(); + ImGui::NewLine(); + } + if (ImGui::BeginCombo("Sorting Group", scene_info.m_sorting_group.c_str())) { + for (int i = 0; i < m_subtitle_db.m_subtitle_groups->m_group_order.size(); ++i) { + const bool isSelected = (scene_info.m_sorting_group_idx == i); + if (ImGui::Selectable(m_subtitle_db.m_subtitle_groups->m_group_order[i].c_str(), + isSelected)) { + // Remove from current group + m_subtitle_db.m_subtitle_groups->remove_scene(scene_info.m_sorting_group, + scene_info.m_name); + // Add to new group + scene_info.m_sorting_group_idx = i; + scene_info.m_sorting_group = m_subtitle_db.m_subtitle_groups->m_group_order.at(i); + m_subtitle_db.m_subtitle_groups->add_scene(scene_info.m_sorting_group, + scene_info.m_name); + } + if (isSelected) { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } + for (int i = 0; i < scene_info.m_lines.size(); i++) { + auto& subtitleLine = scene_info.m_lines.at(i); + std::string summary; + if (subtitleLine.line_utf8.empty()) { + summary = fmt::format("[{}] Clear Screen", subtitleLine.frame); + } else { + summary = fmt::format("[{}] {} - '{}...'", subtitleLine.frame, subtitleLine.speaker_utf8, + subtitleLine.line_utf8.substr(0, 30)); + } + if (subtitleLine.line_utf8.empty()) { + ImGui::PushStyleColor(ImGuiCol_Text, m_disabled_text_color); + } else if (subtitleLine.offscreen) { + ImGui::PushStyleColor(ImGuiCol_Text, m_offscreen_text_color); + } + if (ImGui::TreeNode(fmt::format("{}", i).c_str(), summary.c_str())) { + if (subtitleLine.line_utf8.empty() || subtitleLine.offscreen) { + ImGui::PopStyleColor(); + } + ImGui::InputInt("Starting Frame", &subtitleLine.frame, + ImGuiInputTextFlags_::ImGuiInputTextFlags_CharsDecimal); + ImGui::InputText("Speaker", &subtitleLine.speaker_utf8, + ImGuiInputTextFlags_::ImGuiInputTextFlags_CharsUppercase); + ImGui::InputText("Text", &subtitleLine.line_utf8, + ImGuiInputTextFlags_::ImGuiInputTextFlags_CharsUppercase); + ImGui::Checkbox("Offscreen?", &subtitleLine.offscreen); + ImGui::PushStyleColor(ImGuiCol_Button, m_warning_color); + if (ImGui::Button("Remove Line")) { + scene_info.m_lines.erase(scene_info.m_lines.begin() + i); + } + ImGui::PopStyleColor(); + ImGui::TreePop(); + } else if (subtitleLine.line_utf8.empty() || subtitleLine.offscreen) { + ImGui::PopStyleColor(); + } + } + ImGui::TreePop(); + } else if (base_cutscenes || is_current_scene) { + ImGui::PopStyleColor(); + } + } +} + +void SubtitleEditor::draw_current_cutscene() { + if (!m_current_scene) { + ImGui::PushStyleColor(ImGuiCol_Text, m_disabled_text_color); + } else { + ImGui::PushStyleColor(ImGuiCol_Text, m_selected_text_color); + } + if (ImGui::TreeNode("Currently Selected Movie")) { + ImGui::PopStyleColor(); + if (m_current_scene) { + if (!m_repl.is_connected()) { + ImGui::PushStyleColor(ImGuiCol_Text, m_error_text_color); + ImGui::Text("REPL not connected, can't play!"); + ImGui::PopStyleColor(); + } else if (m_db.count(m_current_scene->m_name) > 0) { + if (ImGui::Button("Play Scene")) { + if (m_db.count(m_current_scene->m_name) == 1) { + repl_execute_cutscene_code(m_db[m_current_scene->m_name]); + } + } + ImGui::SameLine(); + ImGui::PushStyleColor(ImGuiCol_Text, m_disabled_text_color); + ImGui::TextWrapped("You may have to click twice, load times cause issues"); + ImGui::PopStyleColor(); + ImGui::NewLine(); + } + if (ImGui::BeginCombo("Sorting Group", m_current_scene->m_sorting_group.c_str())) { + for (int i = 0; i < m_subtitle_db.m_subtitle_groups->m_group_order.size(); ++i) { + const bool isSelected = (m_current_scene->m_sorting_group_idx == i); + if (ImGui::Selectable(m_subtitle_db.m_subtitle_groups->m_group_order[i].c_str(), + isSelected)) { + // Remove from current group + m_subtitle_db.m_subtitle_groups->remove_scene(m_current_scene->m_sorting_group, + m_current_scene->m_name); + // Add to new group + m_current_scene->m_sorting_group_idx = i; + m_current_scene->m_sorting_group = m_subtitle_db.m_subtitle_groups->m_group_order.at(i); + m_subtitle_db.m_subtitle_groups->add_scene(m_current_scene->m_sorting_group, + m_current_scene->m_name); + } + if (isSelected) { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } + ImGui::InputInt("Frame Number", &m_current_scene_frame, + ImGuiInputTextFlags_::ImGuiInputTextFlags_CharsDecimal); + ImGui::InputText("Text", &m_current_scene_text, + ImGuiInputTextFlags_::ImGuiInputTextFlags_CharsUppercase); + ImGui::InputText("Speaker", &m_current_scene_speaker, + ImGuiInputTextFlags_::ImGuiInputTextFlags_CharsUppercase); + ImGui::Checkbox("Offscreen", &m_current_scene_offscreen); + bool rendered_text_entry_btn = false; + if (m_current_scene_frame < 0 || m_current_scene_text.empty() || + m_current_scene_speaker.empty()) { + ImGui::PushStyleColor(ImGuiCol_Text, m_error_text_color); + ImGui::Text("Can't add a new text entry with the current fields!"); + ImGui::PopStyleColor(); + } else { + rendered_text_entry_btn = true; + if (ImGui::Button("Add Text Entry")) { + m_current_scene->add_line(m_current_scene_frame, "", m_current_scene_text, "", + m_current_scene_speaker, m_current_scene_offscreen); + } + } + if (m_current_scene_frame < 0) { + ImGui::PushStyleColor(ImGuiCol_Text, m_error_text_color); + ImGui::Text("Can't add a clear screen entry with the current fields!"); + ImGui::PopStyleColor(); + } else { + if (rendered_text_entry_btn) { + ImGui::SameLine(); + if (ImGui::Button("Add Clear Screen Entry")) { + m_current_scene->add_line(m_current_scene_frame, "", "", "", "", false); + } + } + } + ImGui::NewLine(); + for (int i = 0; i < m_current_scene->m_lines.size(); i++) { + auto& subtitleLine = m_current_scene->m_lines.at(i); + std::string summary; + if (subtitleLine.line_utf8.empty()) { + summary = fmt::format("[{}] Clear Screen", subtitleLine.frame); + } else { + summary = fmt::format("[{}] {} - '{}...'", subtitleLine.frame, subtitleLine.speaker_utf8, + subtitleLine.line_utf8.substr(0, 30)); + } + if (subtitleLine.line_utf8.empty()) { + ImGui::PushStyleColor(ImGuiCol_Text, m_disabled_text_color); + } else if (subtitleLine.offscreen) { + ImGui::PushStyleColor(ImGuiCol_Text, m_offscreen_text_color); + } + if (ImGui::TreeNode(fmt::format("{}", i).c_str(), summary.c_str())) { + if (subtitleLine.line_utf8.empty() || subtitleLine.offscreen) { + ImGui::PopStyleColor(); + } + ImGui::InputInt("Starting Frame", &subtitleLine.frame, + ImGuiInputTextFlags_::ImGuiInputTextFlags_CharsDecimal); + ImGui::InputText("Speaker", &subtitleLine.speaker_utf8, + ImGuiInputTextFlags_::ImGuiInputTextFlags_CharsUppercase); + ImGui::InputText("Text", &subtitleLine.line_utf8, + ImGuiInputTextFlags_::ImGuiInputTextFlags_CharsUppercase); + ImGui::Checkbox("Offscreen?", &subtitleLine.offscreen); + ImGui::PushStyleColor(ImGuiCol_Button, m_warning_color); + if (ImGui::Button("Remove Line")) { + m_current_scene->m_lines.erase(m_current_scene->m_lines.begin() + i); + } + ImGui::PopStyleColor(); + ImGui::TreePop(); + } else if (subtitleLine.line_utf8.empty() || subtitleLine.offscreen) { + ImGui::PopStyleColor(); + } + } + } else { + ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 0, 0, 255)); + ImGui::Text("Select a Scene from Below!"); + ImGui::PopStyleColor(); + } + ImGui::TreePop(); + } else { + ImGui::PopStyleColor(); + } +} diff --git a/game/tools/subtitles/subtitle_editor.h b/game/tools/subtitles/subtitle_editor.h new file mode 100644 index 0000000000..bddabaa71c --- /dev/null +++ b/game/tools/subtitles/subtitle_editor.h @@ -0,0 +1,84 @@ +#pragma once + +#include "common/serialization/subtitles/subtitles.h" +#include +#include "common/nrepl/ReplClient.h" +#include "third-party/imgui/imgui.h" +#include + +class SubtitleEditorDB { + public: + struct Entry { + std::string entity_type; + std::string process_name; + std::string continue_name; + std::vector move_to; + std::string execute_code; + bool move_first; + std::vector requirements; + }; +}; + +// TODO Later: +// - Hints, these seem less annoying but there are a lot of them + +class SubtitleEditor { + public: + SubtitleEditor(); + void draw_window(); + + private: + void draw_edit_options(); + void draw_repl_options(); + + void draw_all_cutscene_groups(); + void draw_all_scenes(std::string group_name, bool base_cutscenes = false); + void draw_current_cutscene(); + + GameSubtitleDB m_subtitle_db; + std::map m_db = {}; + GameSubtitleSceneInfo* m_current_scene = nullptr; + std::string m_filter; + std::string m_filter_hints; + + ReplClient m_repl; + + int m_current_scene_frame = 0; + std::string m_current_scene_text = ""; + std::string m_current_scene_speaker = ""; + bool m_current_scene_offscreen = false; + + std::string m_new_scene_name = ""; + std::string m_new_scene_group = ""; + + std::string m_new_scene_group_name = ""; + + std::string m_filter_placeholder = "Filter List..."; + + std::optional m_files_saved_successfully = {}; + + int m_base_language = 0; + int m_current_language = 0; + // bool m_base_show_lines = false; + bool m_base_show_missing_cutscenes = true; + + // TODO - let the user customize these colors + ImVec4 m_normal_text_color = ImVec4(1.0f, 0.0f, 1.0f, 1.0f); + int m_selected_text_color = IM_COL32(89, 227, 225, 255); + ImVec4 m_success_text_color = ImVec4(0.0f, 1.0f, 0.0f, 1.0f); + ImVec4 m_error_text_color = ImVec4(1.0f, 0.0f, 0.0f, 1.0f); + ImVec4 m_disabled_text_color = ImVec4(1.0f, 1.0f, 1.0f, 0.7f); + ImVec4 m_warning_color = ImVec4(0.619f, 0.443f, 0.0f, 1.0f); + int m_offscreen_text_color = IM_COL32(240, 242, 102, 255); + // TODO - cycle speaker colors + + void repl_set_continue_point(const std::string_view& continue_point); + void repl_move_jak(const double x, const double y, const double z); + void repl_reset_game(); + std::string repl_get_process_string(const std::string_view& entity_type, + const std::string_view& process_name); + void repl_execute_cutscene_code(const SubtitleEditorDB::Entry& entry); + void repl_rebuild_text(); + + bool is_scene_in_current_lang(const std::string& scene_name); +}; diff --git a/goal_src/goal-lib.gc b/goal_src/goal-lib.gc index 2ad67de243..2eddf212db 100644 --- a/goal_src/goal-lib.gc +++ b/goal_src/goal-lib.gc @@ -915,6 +915,11 @@ `(make-group "engine") ) +(defmacro make-text () + "Make Text" + `(make-group "text") + ) + ;;;;;;;;;;;;;;;;;;; ;; enum stuff ;;;;;;;;;;;;;;;;;;; diff --git a/goalc/CMakeLists.txt b/goalc/CMakeLists.txt index cdeea7e8ee..24b84263bb 100644 --- a/goalc/CMakeLists.txt +++ b/goalc/CMakeLists.txt @@ -26,12 +26,11 @@ add_library(compiler compiler/compilation/Type.cpp compiler/compilation/State.cpp compiler/compilation/Static.cpp - compiler/nrepl/ReplServer.cpp compiler/Util.cpp data_compiler/game_text_common.cpp data_compiler/dir_tpages.cpp - data_compiler/DataObjectGenerator.cpp data_compiler/game_count.cpp + data_compiler/DataObjectGenerator.cpp debugger/Debugger.cpp debugger/DebugInfo.cpp listener/Listener.cpp diff --git a/goalc/compiler/Compiler.cpp b/goalc/compiler/Compiler.cpp index 8aa6781d37..52100e7a4c 100644 --- a/goalc/compiler/Compiler.cpp +++ b/goalc/compiler/Compiler.cpp @@ -1,5 +1,3 @@ -#include "nrepl/ReplServer.h" // this import has to come first because WinSock sucks - #include "Compiler.h" #include #include diff --git a/goalc/data_compiler/dir_tpages.cpp b/goalc/data_compiler/dir_tpages.cpp index 93b2449803..de2e3b1917 100644 --- a/goalc/data_compiler/dir_tpages.cpp +++ b/goalc/data_compiler/dir_tpages.cpp @@ -32,4 +32,4 @@ void compile_dir_tpages(const std::string& filename) { file_util::create_dir_if_needed(file_util::get_file_path({"out", "obj"})); file_util::write_binary_file(file_util::get_file_path({"out", "obj", "dir-tpages.go"}), data.data(), data.size()); -} \ No newline at end of file +} diff --git a/goalc/data_compiler/game_count.cpp b/goalc/data_compiler/game_count.cpp index 0edcf726bd..c396f2553c 100644 --- a/goalc/data_compiler/game_count.cpp +++ b/goalc/data_compiler/game_count.cpp @@ -74,4 +74,4 @@ void compile_game_count(const std::string& filename) { file_util::create_dir_if_needed(file_util::get_file_path({"out", "obj"})); file_util::write_binary_file(file_util::get_file_path({"out", "obj", "game-cnt.go"}), result.data(), result.size()); -} \ No newline at end of file +} diff --git a/goalc/data_compiler/game_text_common.cpp b/goalc/data_compiler/game_text_common.cpp index 7730e16114..7ff7f36c71 100644 --- a/goalc/data_compiler/game_text_common.cpp +++ b/goalc/data_compiler/game_text_common.cpp @@ -20,37 +20,9 @@ #include "common/util/FontUtils.h" #include "common/goos/ParseHelpers.h" #include "third-party/fmt/core.h" +#include "common/serialization/subtitles/subtitles.h" namespace { -int64_t get_int(const goos::Object& obj) { - if (obj.is_int()) { - return obj.integer_obj.value; - } - throw std::runtime_error(obj.print() + " was supposed to be an integer, but isn't"); -} - -const goos::Object& car(const goos::Object& x) { - if (!x.is_pair()) { - throw std::runtime_error("invalid pair"); - } - - return x.as_pair()->car; -} - -const goos::Object& cdr(const goos::Object& x) { - if (!x.is_pair()) { - throw std::runtime_error("invalid pair"); - } - - return x.as_pair()->cdr; -} - -std::string get_string(const goos::Object& x) { - if (x.is_string()) { - return x.as_string()->data; - } - throw std::runtime_error(x.print() + " was supposed to be a string, but isn't"); -} std::string uppercase(const std::string& in) { std::string result; @@ -64,94 +36,6 @@ std::string uppercase(const std::string& in) { return result; } -/*! - * Parse a game text file. - * Information is added to the game text database. - * - * The file should begin with (language-id x y z...) with the given language IDs. - * Each entry should be (id "line for 1st language" "line for 2nd language" ...) - * This adds the text line to each of the specified languages. - */ -void parse_text(const goos::Object& data, GameTextVersion text_ver, GameTextDB& db) { - auto font = get_font_bank(text_ver); - std::vector> banks; - std::string possible_group_name; - - for_each_in_list(data.as_pair()->cdr, [&](const goos::Object& obj) { - if (obj.is_pair()) { - auto& head = car(obj); - if (head.is_symbol() && head.as_symbol()->name == "language-id") { - if (banks.size() != 0) { - throw std::runtime_error("Languages have been set multiple times."); - } - - if (cdr(obj).is_empty_list()) { - throw std::runtime_error("At least one language must be set."); - } - - if (possible_group_name.empty()) { - throw std::runtime_error("Text group must be set before languages."); - } - - for_each_in_list(cdr(obj), [&](const goos::Object& obj) { - auto lang = get_int(obj); - if (!db.bank_exists(possible_group_name, lang)) { - // database has no lang in this group yet - banks.push_back(db.add_bank(possible_group_name, std::make_shared(lang))); - } else { - banks.push_back(db.bank_by_id(possible_group_name, lang)); - } - }); - } else if (head.is_symbol() && head.as_symbol()->name == "group-name") { - if (!possible_group_name.empty()) { - throw std::runtime_error("group-name has been set multiple times."); - } - - possible_group_name = get_string(car(cdr(obj))); - - if (possible_group_name.empty()) { - throw std::runtime_error("invalid group-name."); - } - - if (!cdr(cdr(obj)).is_empty_list()) { - throw std::runtime_error("group-name has too many arguments"); - } - } - - else if (head.is_int()) { - if (banks.size() == 0) { - throw std::runtime_error("At least one language must be set before defining entries."); - } - int i = 0; - int id = head.as_int(); - for_each_in_list(cdr(obj), [&](const goos::Object& entry) { - if (entry.is_string()) { - if (i >= int(banks.size())) { - throw std::runtime_error(fmt::format("Too many strings in text id #x{:x}", id)); - } - - auto line = font->convert_utf8_to_game(entry.as_string()->data); - banks[i++]->set_line(id, line); - } else { - throw std::runtime_error(fmt::format("Non-string value in text id #x{:x}", id)); - } - }); - if (i != int(banks.size())) { - throw std::runtime_error( - fmt::format("Not enough strings specified in text id #x{:x}", id)); - } - } else { - throw std::runtime_error("Invalid game text file entry: " + head.print()); - } - } else { - throw std::runtime_error("Invalid game text file"); - } - }); - if (banks.size() == 0) { - throw std::runtime_error("At least one language must be set."); - } -} - /* (deftype game-text (structure) ((id uint32 :offset-assert 0) @@ -202,139 +86,6 @@ void compile_text(GameTextDB& db) { } } -/*! - * Parse a game subtitle file. - * Information is added to the game subtitles database. - * - * The file should begin with (language-id x y z...) for the given language IDs. - * Each scene should be (scene-name ... ) - * This adds the subtitle to each of the specified languages. - */ -void parse_subtitle(const goos::Object& data, GameTextVersion text_ver, GameSubtitleDB& db) { - auto font = get_font_bank(text_ver); - std::map> banks; - - for_each_in_list(data.as_pair()->cdr, [&](const goos::Object& obj) { - if (obj.is_pair()) { - auto& head = car(obj); - if (head.is_symbol() && head.as_symbol()->name == "language-id") { - if (banks.size() != 0) { - throw std::runtime_error("Languages have been set multiple times."); - } - - if (cdr(obj).is_empty_list()) { - throw std::runtime_error("At least one language must be set."); - } - - for_each_in_list(cdr(obj), [&](const goos::Object& obj) { - auto lang = get_int(obj); - if (!db.bank_exists(lang)) { - // database has no lang yet - banks[lang] = db.add_bank(std::make_shared(lang)); - } else { - banks[lang] = db.bank_by_id(lang); - } - }); - } - - else if (head.is_string() || head.is_int()) { - if (banks.size() == 0) { - throw std::runtime_error("At least one language must be set before defining scenes."); - } - auto kind = SubtitleSceneKind::Movie; - int id = 0; - auto entries = cdr(obj); - if (head.is_int()) { - kind = SubtitleSceneKind::Hint; - } else if (car(entries).is_symbol()) { - const auto& parm = car(entries).as_symbol()->name; - if (parm == ":hint") { - entries = cdr(entries); - id = car(entries).as_int(); - kind = SubtitleSceneKind::HintNamed; - } else { - throw std::runtime_error("Unknown parameter for subtitle scene"); - } - entries = cdr(entries); - } - - GameSubtitleSceneInfo scene(kind); - if (kind == SubtitleSceneKind::Movie || kind == SubtitleSceneKind::HintNamed) { - scene.set_name(head.as_string()->data); - } else if (kind == SubtitleSceneKind::Hint) { - id = head.as_int(); - } - scene.set_id(id); - - for_each_in_list(entries, [&](const goos::Object& entry) { - if (entry.is_pair()) { - // expected formats: - // (time ) - // all arguments have default values. the arguments are: - // "speaker" "line" - two strings. one for the speaker's name and one for the actual - // line. speaker can be empty. default is just empty string. - // :offscreen - speaker is offscreen. default is not offscreen. - - if (!car(entry).is_int()) { - throw std::runtime_error("Each entry must start with a timestamp (number)"); - } - - auto time = car(entry).as_int(); - goos::StringObject *speaker = nullptr, *line = nullptr; - bool offscreen = false; - if (scene.kind() == SubtitleSceneKind::Hint || - scene.kind() == SubtitleSceneKind::HintNamed) { - offscreen = true; - } - for_each_in_list(cdr(entry), [&](const goos::Object& arg) { - if (arg.is_string()) { - if (!speaker) { - speaker = arg.as_string(); - } else if (!line) { - line = arg.as_string(); - } else { - throw std::runtime_error("Invalid string in subtitle entry"); - } - } else if (speaker && !line) { - throw std::runtime_error( - "Invalid object in subtitle entry, expecting actual line string after speaker"); - } else if (arg.is_symbol()) { - if (scene.kind() == SubtitleSceneKind::Movie && - arg.as_symbol()->name == ":offscreen") { - offscreen = true; - } else { - throw std::runtime_error( - fmt::format("Unknown parameter {} in subtitle", arg.as_symbol()->name)); - } - } - }); - auto line_str = font->convert_utf8_to_game(line ? line->data : ""); - auto speaker_str = font->convert_utf8_to_game(speaker ? speaker->data : ""); - scene.add_line(time, line_str, speaker_str, offscreen); - } else { - throw std::runtime_error("Each entry must be a list"); - } - }); - for (auto& [lang, bank] : banks) { - if (!bank->scene_exists(scene.name())) { - bank->add_scene(scene); - } else { - auto& old_scene = bank->scene_by_name(scene.name()); - old_scene.from_other_scene(scene); - } - } - } else { - throw std::runtime_error("Invalid game subtitles file entry: " + head.print()); - } - } else { - throw std::runtime_error("Invalid game subtitles file"); - } - }); - if (banks.size() == 0) { - throw std::runtime_error("At least one language must be set."); - } -} - /*! * Write game subtitle data to a file. Uses the V2 object format which is identical between GOAL and * OpenGOAL. @@ -412,32 +163,7 @@ void compile_game_subtitle(const std::vector& filenames, for (auto& filename : filenames) { fmt::print("[Build Game Subtitle] {}\n", filename.c_str()); auto code = reader.read_from_file({filename}); - parse_subtitle(code, text_ver, db); + parse_subtitle(code, text_ver, db, filename); } compile_subtitle(db); } - -static const std::unordered_map s_text_ver_enum_map = { - {"jak1-v1", GameTextVersion::JAK1_V1}}; - -void open_text_project(const std::string& kind, - const std::string& filename, - std::unordered_map>& inputs) { - goos::Reader reader; - auto& proj = reader.read_from_file({filename}).as_pair()->cdr.as_pair()->car; - if (!proj.is_pair() || !proj.as_pair()->car.is_symbol() || - proj.as_pair()->car.as_symbol()->name != kind) { - throw std::runtime_error(fmt::format("invalid {} project", kind)); - } - - goos::for_each_in_list(proj.as_pair()->cdr, [&](const goos::Object& o) { - if (!o.is_pair()) { - throw std::runtime_error(fmt::format("invalid entry in {} project", kind)); - } - - auto& ver = o.as_pair()->car.as_symbol()->name; - auto& in = o.as_pair()->cdr.as_pair()->car.as_string()->data; - - inputs[s_text_ver_enum_map.at(ver)].push_back(in); - }); -} diff --git a/goalc/data_compiler/game_text_common.h b/goalc/data_compiler/game_text_common.h index 24f528ced6..7f0699cfb9 100644 --- a/goalc/data_compiler/game_text_common.h +++ b/goalc/data_compiler/game_text_common.h @@ -6,156 +6,7 @@ #include #include #include - -/*! - * The text bank contains all lines (accessed with an ID) for a language. - */ -class GameTextBank { - public: - GameTextBank(int lang_id) : m_lang_id(lang_id) {} - - int lang() const { return m_lang_id; } - const std::map& lines() const { return m_lines; } - - bool line_exists(int id) const { return m_lines.find(id) != m_lines.end(); } - std::string line(int id) { return m_lines.at(id); } - void set_line(int id, std::string line) { m_lines[id] = line; } - - private: - int m_lang_id; - std::map m_lines; -}; - -/*! - * The text database contains a text bank for each language for each text group. - * Each text bank contains a list of text lines. Very simple. - */ -class GameTextDB { - public: - const std::unordered_map>>& groups() - const { - return m_banks; - } - const std::map>& banks(std::string group) const { - return m_banks.at(group); - } - - bool bank_exists(std::string group, int id) const { - if (m_banks.find(group) == m_banks.end()) - return false; - return m_banks.at(group).find(id) != m_banks.at(group).end(); - } - - std::shared_ptr add_bank(std::string group, std::shared_ptr bank) { - ASSERT(!bank_exists(group, bank->lang())); - m_banks[group][bank->lang()] = bank; - return bank; - } - std::shared_ptr bank_by_id(std::string group, int id) { - if (!bank_exists(group, id)) { - return nullptr; - } - return m_banks.at(group).at(id); - } - - private: - std::unordered_map>> m_banks; -}; - -/*! - * The subtitle scene info (accessed through the scene name) contains all lines and their timestamps - * and other settings. - */ -enum class SubtitleSceneKind { Invalid = -1, Movie = 0, Hint = 1, HintNamed = 2 }; -class GameSubtitleSceneInfo { - public: - struct SubtitleLine { - SubtitleLine(int frame, std::string line, std::string speaker, bool offscreen) - : frame(frame), line(line), speaker(speaker), offscreen(offscreen) {} - - int frame; - std::string line; - std::string speaker; - bool offscreen; - }; - - GameSubtitleSceneInfo() {} - GameSubtitleSceneInfo(SubtitleSceneKind kind) : m_kind(kind) {} - - const std::string& name() const { return m_name; } - const std::vector& lines() const { return m_lines; } - int id() const { return m_id; } - SubtitleSceneKind kind() const { return m_kind; } - - void clear_lines() { m_lines.clear(); } - void set_name(const std::string& new_name) { m_name = new_name; } - void set_id(int new_id) { m_id = new_id; } - void from_other_scene(GameSubtitleSceneInfo& scene) { - m_name = scene.name(); - m_lines = scene.lines(); - m_kind = scene.kind(); - m_id = scene.id(); - } - - void add_line(int frame, std::string line, std::string speaker, bool offscreen) { - m_lines.emplace_back(frame, line, speaker, offscreen); - } - - private: - std::string m_name; - int m_id; - std::vector m_lines; - SubtitleSceneKind m_kind; -}; - -/*! - * The subtitle bank contains subtitles for all scenes in a language. - */ -class GameSubtitleBank { - public: - GameSubtitleBank(int lang_id) : m_lang_id(lang_id) {} - - int lang() const { return m_lang_id; } - const std::map& scenes() const { return m_scenes; } - - bool scene_exists(const std::string& name) const { return m_scenes.find(name) != m_scenes.end(); } - GameSubtitleSceneInfo& scene_by_name(const std::string& name) { return m_scenes.at(name); } - void add_scene(GameSubtitleSceneInfo& scene) { - ASSERT(!scene_exists(scene.name())); - m_scenes[scene.name()] = scene; - } - - private: - int m_lang_id; - - std::map m_scenes; -}; - -/*! - * The subtitles database contains a subtitles bank for each language. - * Each subtitles bank contains a series of subtitle scene infos. - */ -class GameSubtitleDB { - public: - const std::map>& banks() const { return m_banks; } - - bool bank_exists(int id) const { return m_banks.find(id) != m_banks.end(); } - - std::shared_ptr add_bank(std::shared_ptr bank) { - ASSERT(!bank_exists(bank->lang())); - m_banks[bank->lang()] = bank; - return bank; - } - std::shared_ptr bank_by_id(int id) { - if (!bank_exists(id)) { - return nullptr; - } - return m_banks.at(id); - } - - private: - std::map> m_banks; -}; +#include "common/serialization/subtitles/subtitles.h" void compile_game_text(const std::vector& filenames, GameTextVersion text_ver, diff --git a/goalc/main.cpp b/goalc/main.cpp index 9d9154a8cd..0832a25e52 100644 --- a/goalc/main.cpp +++ b/goalc/main.cpp @@ -10,7 +10,7 @@ #include "common/goos/ReplUtils.h" #include -#include +#include "common/nrepl/ReplServer.h" void setup_logging(bool verbose) { lg::set_file(file_util::get_file_path({"log/compiler.txt"})); diff --git a/goalc/make/Tools.cpp b/goalc/make/Tools.cpp index 2c06214a14..d387bb75bf 100644 --- a/goalc/make/Tools.cpp +++ b/goalc/make/Tools.cpp @@ -177,6 +177,8 @@ bool SubtitleTool::needs_run(const ToolInput& task) { bool SubtitleTool::run(const ToolInput& task) { GameSubtitleDB db; + db.m_subtitle_groups = std::make_unique(); + db.m_subtitle_groups->hydrate_from_asset_file(); std::unordered_map> inputs; open_text_project("subtitle", task.input.at(0), inputs); for (auto& [ver, in] : inputs) { diff --git a/third-party/imgui/CMakeLists.txt b/third-party/imgui/CMakeLists.txt index a92a99a7c7..457fefa544 100644 --- a/third-party/imgui/CMakeLists.txt +++ b/third-party/imgui/CMakeLists.txt @@ -5,6 +5,6 @@ add_library(imgui imgui_tables.cpp imgui_widgets.cpp imgui_impl_glfw.cpp - imgui_impl_opengl3.cpp) + imgui_impl_opengl3.cpp "imgui_stdlib.cpp" "imgui_stdlib.h") target_link_libraries(imgui glfw) diff --git a/third-party/imgui/imgui_stdlib.cpp b/third-party/imgui/imgui_stdlib.cpp new file mode 100644 index 0000000000..bffb7fa7fa --- /dev/null +++ b/third-party/imgui/imgui_stdlib.cpp @@ -0,0 +1,82 @@ +// dear imgui: wrappers for C++ standard library (STL) types (std::string, etc.) +// This is also an example of how you may wrap your own similar types. + +// Changelog: +// - v0.10: Initial version. Added InputText() / InputTextMultiline() calls with std::string + +#include "imgui.h" +#include "imgui_stdlib.h" + +struct InputTextCallback_UserData { + std::string* Str; + ImGuiInputTextCallback ChainCallback; + void* ChainCallbackUserData; +}; + +static int InputTextCallback(ImGuiInputTextCallbackData* data) { + InputTextCallback_UserData* user_data = (InputTextCallback_UserData*)data->UserData; + if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) { + // Resize string callback + // If for some reason we refuse the new length (BufTextLen) and/or capacity (BufSize) we need to + // set them back to what we want. + std::string* str = user_data->Str; + IM_ASSERT(data->Buf == str->c_str()); + str->resize(data->BufTextLen); + data->Buf = (char*)str->c_str(); + } else if (user_data->ChainCallback) { + // Forward to user callback, if any + data->UserData = user_data->ChainCallbackUserData; + return user_data->ChainCallback(data); + } + return 0; +} + +bool ImGui::InputText(const char* label, + std::string* str, + ImGuiInputTextFlags flags, + ImGuiInputTextCallback callback, + void* user_data) { + IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0); + flags |= ImGuiInputTextFlags_CallbackResize; + + InputTextCallback_UserData cb_user_data; + cb_user_data.Str = str; + cb_user_data.ChainCallback = callback; + cb_user_data.ChainCallbackUserData = user_data; + return InputText(label, (char*)str->c_str(), str->capacity() + 1, flags, InputTextCallback, + &cb_user_data); +} + +bool ImGui::InputTextMultiline(const char* label, + std::string* str, + const ImVec2& size, + ImGuiInputTextFlags flags, + ImGuiInputTextCallback callback, + void* user_data) { + IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0); + flags |= ImGuiInputTextFlags_CallbackResize; + + InputTextCallback_UserData cb_user_data; + cb_user_data.Str = str; + cb_user_data.ChainCallback = callback; + cb_user_data.ChainCallbackUserData = user_data; + return InputTextMultiline(label, (char*)str->c_str(), str->capacity() + 1, size, flags, + InputTextCallback, &cb_user_data); +} + +bool ImGui::InputTextWithHint(const char* label, + const char* hint, + std::string* str, + ImGuiInputTextFlags flags, + ImGuiInputTextCallback callback, + void* user_data) { + IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0); + flags |= ImGuiInputTextFlags_CallbackResize; + + InputTextCallback_UserData cb_user_data; + cb_user_data.Str = str; + cb_user_data.ChainCallback = callback; + cb_user_data.ChainCallbackUserData = user_data; + return InputTextWithHint(label, hint, (char*)str->c_str(), str->capacity() + 1, flags, + InputTextCallback, &cb_user_data); +} diff --git a/third-party/imgui/imgui_stdlib.h b/third-party/imgui/imgui_stdlib.h new file mode 100644 index 0000000000..77b1bd825c --- /dev/null +++ b/third-party/imgui/imgui_stdlib.h @@ -0,0 +1,31 @@ +// dear imgui: wrappers for C++ standard library (STL) types (std::string, etc.) +// This is also an example of how you may wrap your own similar types. + +// Changelog: +// - v0.10: Initial version. Added InputText() / InputTextMultiline() calls with std::string + +#pragma once + +#include + +namespace ImGui { +// ImGui::InputText() with std::string +// Because text input needs dynamic resizing, we need to setup a callback to grow the capacity +IMGUI_API bool InputText(const char* label, + std::string* str, + ImGuiInputTextFlags flags = 0, + ImGuiInputTextCallback callback = NULL, + void* user_data = NULL); +IMGUI_API bool InputTextMultiline(const char* label, + std::string* str, + const ImVec2& size = ImVec2(0, 0), + ImGuiInputTextFlags flags = 0, + ImGuiInputTextCallback callback = NULL, + void* user_data = NULL); +IMGUI_API bool InputTextWithHint(const char* label, + const char* hint, + std::string* str, + ImGuiInputTextFlags flags = 0, + ImGuiInputTextCallback callback = NULL, + void* user_data = NULL); +} // namespace ImGui