diff --git a/projects/Core/api/scenes/create_objects.cpp b/projects/Core/api/scenes/create_objects.cpp index 6ddbf100e69..b38416de5da 100644 --- a/projects/Core/api/scenes/create_objects.cpp +++ b/projects/Core/api/scenes/create_objects.cpp @@ -44,12 +44,10 @@ namespace core::api::scenes { std::unordered_map object_spawns; std::unordered_map> pending_object_spawns_by_scene; - void on_loading_callback(std::string_view scene_name, app::SceneState__Enum state, app::GameObject* scene_root) { - if (state == app::SceneState__Enum::Loaded && scene_root != nullptr) { - auto scene_name_string = std::string(scene_name); - - for (auto spawn : pending_object_spawns_by_scene[scene_name_string]) { - auto prefab = il2cpp::unity::find_child(scene_root, spawn->path); + void on_loading_callback(SceneLoadEventMetadata* metadata) { + if (metadata->state == app::SceneState__Enum::Loaded && metadata->scene->fields.SceneRoot != nullptr) { + for (auto spawn : pending_object_spawns_by_scene[metadata->scene_name]) { + auto prefab = il2cpp::unity::find_child(metadata->scene->fields.SceneRoot, spawn->path); if (!il2cpp::unity::is_valid(prefab)) return; @@ -69,10 +67,10 @@ namespace core::api::scenes { core::api::graphics::shaders::duplicate_materials(spawn->game_object); if (spawn->on_loaded != nullptr) - spawn->on_loaded(scene_name, spawn->name, scene_root, spawn->game_object); + spawn->on_loaded(metadata->scene_name, spawn->name, il2cpp::unity::get_game_object(metadata->scene->fields.SceneRoot), spawn->game_object); } - pending_object_spawns_by_scene.erase(scene_name_string); + pending_object_spawns_by_scene.erase(metadata->scene_name); } } diff --git a/projects/Core/api/scenes/scene_load.cpp b/projects/Core/api/scenes/scene_load.cpp index d7301d99d63..e6f224dd0ed 100644 --- a/projects/Core/api/scenes/scene_load.cpp +++ b/projects/Core/api/scenes/scene_load.cpp @@ -81,7 +81,7 @@ namespace core::api::scenes { } for (auto on_load_callback : pending_scene.scene_loading_callbacks) { - on_load_callback(pending_scene.scene_name, state, scene_root_go); + on_load_callback(&event); } } } @@ -195,7 +195,10 @@ namespace core::api::scenes { scene_manager_scene->fields.PreventUnloading = keep_preloaded; if (callback != nullptr) { - callback(scene, scene_manager_scene->fields.m_currentState, scene_root_go); + SceneLoadEventMetadata metadata { + std::string(scene), scene_manager_scene->fields.m_currentState, scene_manager_scene, + }; + callback(&metadata); } } else if (!ScenesManager::SceneIsLoading(scenes_manager, scene_meta->fields.SceneMoonGuid)) { ScenesManager::RequestAdditivelyLoadScene(scenes_manager, scene_meta, async, true, true, true, false); @@ -312,10 +315,9 @@ namespace core::api::scenes { } } - void on_load_spawn(std::string_view scene_name, app::SceneState__Enum state, app::GameObject* scene_root) { - if (state == app::SceneState__Enum::Loaded && scene_root != nullptr) { - auto root = il2cpp::unity::get_component(scene_root, types::SceneRoot::get_class()); - initial_values_handle = il2cpp::gchandle_new(root->fields.MetaData->fields.InitialValuesWisp, false); + void on_load_spawn(SceneLoadEventMetadata* metadata) { + if (metadata->state == app::SceneState__Enum::Loaded && metadata->scene->fields.SceneRoot != nullptr) { + initial_values_handle = il2cpp::gchandle_new(metadata->scene->fields.SceneRoot->fields.MetaData->fields.InitialValuesWisp, false); } } diff --git a/projects/Core/api/scenes/scene_load.h b/projects/Core/api/scenes/scene_load.h index 783670308ce..84866b82717 100644 --- a/projects/Core/api/scenes/scene_load.h +++ b/projects/Core/api/scenes/scene_load.h @@ -18,7 +18,7 @@ namespace core::api::scenes { app::SceneManagerScene* scene; }; - using scene_loading_callback = void (*)(std::string_view scene_name, app::SceneState__Enum state, app::GameObject* scene_root); + using scene_loading_callback = void (*)(SceneLoadEventMetadata* metadata); CORE_DLLEXPORT app::ScenesManager* get_scenes_manager(); diff --git a/projects/Core/task.h b/projects/Core/task.h index 3ff13e0dfb0..257b538828d 100644 --- a/projects/Core/task.h +++ b/projects/Core/task.h @@ -4,7 +4,7 @@ #include -namespace core::task { - CORE_DLLEXPORT void schedule(float seconds, std::function task); +namespace core::events { + CORE_DLLEXPORT void schedule_task(float seconds, std::function task); CORE_DLLEXPORT void schedule_for_next_update(std::function task); -} // namespace core::tasks \ No newline at end of file +} // namespace core::tasks diff --git a/projects/Randomizer/CMakeLists.txt b/projects/Randomizer/CMakeLists.txt index 43e00b310d1..7b19fd09500 100644 --- a/projects/Randomizer/CMakeLists.txt +++ b/projects/Randomizer/CMakeLists.txt @@ -119,7 +119,7 @@ set( "game/shops/spirit_light_spent.cpp" "game/shops/tuley.cpp" "game/shops/twillen.cpp" - "game/spawning_preloading_teleporting.cpp" + "game/spawning_preloading_teleporting_and_race_lobby_yes_this_file_needs_to_be_split.cpp" "ghosts/plugins/update_active_animations_plugin.cpp" "ghosts/plugins.cpp" "ghosts.cpp" diff --git a/projects/Randomizer/features/credits.cpp b/projects/Randomizer/features/credits.cpp index 213cdb63d85..688a3404014 100644 --- a/projects/Randomizer/features/credits.cpp +++ b/projects/Randomizer/features/credits.cpp @@ -24,11 +24,11 @@ namespace randomizer::features::credits { constexpr char CREDITS_PATH_FMT[] = "{}\\credits"; randomizer::messages::CreditsController credits; - void credits_scene_loaded_callback(std::string_view scene_name, app::SceneState__Enum state, app::GameObject* scene_root) { - if (state == app::SceneState__Enum::Loaded && scene_root != nullptr) { + void credits_scene_loaded_callback(core::api::scenes::SceneLoadEventMetadata* metadata) { + if (metadata->state == app::SceneState__Enum::Loaded && metadata->scene->fields.SceneRoot != nullptr) { credits.reset(); credits.load(fmt::format(CREDITS_PATH_FMT, modloader::base_path().string())); - auto credits_go = il2cpp::unity::find_child(scene_root, "credits"); + auto credits_go = il2cpp::unity::find_child(metadata->scene->fields.SceneRoot, "credits"); auto cred_cont = il2cpp::unity::get_component(credits_go, types::CreditsController::get_class()); auto timeline = cred_cont->fields.CreditsTimeline; il2cpp::invoke_virtual(timeline, reinterpret_cast(types::TimelineEntity::get_class()), "StartPlayback"); @@ -55,9 +55,9 @@ namespace randomizer::features::credits { } } - void ending_scene_loaded_callback(std::string_view scene_name, app::SceneState__Enum state, app::GameObject* scene_root) { - if (state == app::SceneState__Enum::Loaded && scene_root != nullptr) { - auto master_timeline_go = il2cpp::unity::find_child(scene_root, "master2.0"); + void ending_scene_loaded_callback(core::api::scenes::SceneLoadEventMetadata* metadata) { + if (metadata->state == app::SceneState__Enum::Loaded && metadata->scene->fields.SceneRoot != nullptr) { + auto master_timeline_go = il2cpp::unity::find_child(metadata->scene->fields.SceneRoot, "master2.0"); il2cpp::unity::destroy_object(master_timeline_go); core::api::game::player::set_position(0.f, 0.f); diff --git a/projects/Randomizer/game/spawning_preloading_teleporting.cpp b/projects/Randomizer/game/spawning_preloading_teleporting.cpp deleted file mode 100644 index e75b8e5ecbe..00000000000 --- a/projects/Randomizer/game/spawning_preloading_teleporting.cpp +++ /dev/null @@ -1,286 +0,0 @@ -#include -#include - -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace modloader; -using namespace app::classes; -using modloader::win::console::console_send; - -namespace randomizer::game { - namespace { - enum class TeleportState { - None, - Teleport, - PostTeleport, - }; - - TeleportState teleport_state = TeleportState::None; - app::Vector3 teleport_position; - bool handling_start = false; - - IL2CPP_INTERCEPT(SeinCharacter, void, FixedUpdate, (app::SeinCharacter * this_ptr)) { - // Don't teleport during cutscene skips, causes crashes. - if (teleport_state == TeleportState::Teleport) { - SeinCharacter::set_Position(this_ptr, teleport_position); - teleport_state = TeleportState::PostTeleport; - } else if (teleport_state == TeleportState::PostTeleport) { - core::api::game::player::snap_camera(); - - ScenesManager::EnableDisabledScenesAtPosition(core::api::scenes::get_scenes_manager(), false, false); - SeinCharacter::set_Position(this_ptr, teleport_position); - - if (handling_start) { - handling_start = false; - core::api::faderb::fade_out(0.3f); - } - - teleport_state = TeleportState::None; - - auto area_map_ui = types::AreaMapUI::get_class()->static_fields->Instance; - auto quests_ui = types::QuestsUI::get_class()->static_fields->Instance; - AreaMapNavigation::SetLocationPlayer(area_map_ui->fields._Navigation_k__BackingField); - QuestsUI::UpdateDescriptionUI_2(quests_ui, nullptr); - } - - next::SeinCharacter::FixedUpdate(this_ptr); - } - - core::api::uber_states::UberState intro_cutscene(static_cast(21786), 48748); - - IL2CPP_INTERCEPT(Moon::uberSerializationWisp::PlayerUberStateAreaMapInformation, void, SetAreaState, (app::PlayerUberStateAreaMapInformation * this_ptr, app::GameWorldAreaID__Enum area_id, int index, app::WorldMapAreaState__Enum state, app::Vector3 position)) { - if (handling_start && state == app::WorldMapAreaState__Enum::Visited) { - state = app::WorldMapAreaState__Enum::Discovered; - } - - next::Moon::uberSerializationWisp::PlayerUberStateAreaMapInformation::SetAreaState(this_ptr, area_id, index, state, position); - } - - // Dont cancel loads during teleportation. - IL2CPP_INTERCEPT(ScenesManager, bool, CancelScene, (app::ScenesManager * this_ptr, app::SceneManagerScene* scene)) { - if (teleport_state != TeleportState::Teleport) { - return next::ScenesManager::CancelScene(this_ptr, scene); - } - - return false; - } - - // The game calls set_CurrentSlotIndex on startup. We set this variable to true - // for this to not start preloading too early. - bool prevent_preload_on_selecting_empty_save = false; - - // region Preload when selecting empty save slot - app::SaveSlotsUI* get_save_slots_ui() { - auto save_slots_ui_klass = types::SaveSlotsUI::get_class(); - return save_slots_ui_klass->static_fields->Instance; - } - - IL2CPP_INTERCEPT(TitleScreenManager, void, SetScreen, (app::TitleScreenManager_Screen__Enum screen)) { - next::TitleScreenManager::SetScreen(screen); - - if (screen == app::TitleScreenManager_Screen__Enum::SaveSlots) { - auto save_slots_ui = get_save_slots_ui(); - if (save_slots_ui != nullptr) { - auto save_slot_ui = SaveSlotsUI::get_CurrentSaveSlot(save_slots_ui); - SaveSlotUI::SetBusy(save_slot_ui, false); - } - } - } - - std::set pending_scenes_to_preload; - std::set scenes_to_preload; - - void on_scene_loading(std::string_view scene_name, app::SceneState__Enum state, app::GameObject* scene_root) { - if (state == app::SceneState__Enum::Loaded || state == app::SceneState__Enum::LoadingCancelled) { - if (!pending_scenes_to_preload.erase(scene_name.data())) { - return; - } - - if (pending_scenes_to_preload.empty()) { - auto save_slots_ui = get_save_slots_ui(); - - if (save_slots_ui != nullptr) { - auto save_slot_ui = SaveSlotsUI::get_CurrentSaveSlot(save_slots_ui); - SaveSlotUI::SetBusy(save_slot_ui, false); - } - } - } - } - - IL2CPP_INTERCEPT(SaveSlotsUI, void, OnEnable, (app::SaveSlotsUI * this_ptr)) { - ScopedSetter setter(prevent_preload_on_selecting_empty_save, true); - next::SaveSlotsUI::OnEnable(this_ptr); - } - - IL2CPP_INTERCEPT(SaveSlotsManager, void, set_CurrentSlotIndex, (int index)) { - next::SaveSlotsManager::set_CurrentSlotIndex(index); - - if (!prevent_preload_on_selecting_empty_save) { - auto slot_info = SaveSlotsManager::SlotByIndex(index); - - if (slot_info == nullptr) { - console_send(fmt::format("Selected empty index {}", index)); - - auto save_slots_ui = get_save_slots_ui(); - - if (save_slots_ui != nullptr) { - auto save_slot_ui = SaveSlotsUI::get_CurrentSaveSlot(save_slots_ui); - - auto scene_names = core::api::scenes::get_scenes_at_position(game_seed().info().start_position); - - for (const auto& scene_name : scenes_to_preload) { - if (!scene_names.contains(scene_name)) { - core::api::scenes::unload_scene(scene_name, false); - } - } - - pending_scenes_to_preload.clear(); - for (const auto& scene_name : scene_names) { - if (!core::api::scenes::scene_is_loaded(scene_name)) { - pending_scenes_to_preload.emplace(scene_name); - scenes_to_preload.emplace(scene_name); - core::api::scenes::force_load_scene(scene_name, &on_scene_loading, true, true); - } - } - - if (!pending_scenes_to_preload.empty()) { - SaveSlotUI::SetBusy(save_slot_ui, true); - } - } - } - } - } - // endregion - - app::WaitAction* empty_slot_pressed_wait = nullptr; - - IL2CPP_INTERCEPT(WaitAction, void, Perform, (app::WaitAction * this_ptr, app::IContext* context)) { - next::WaitAction::Perform(this_ptr, context); - - // If this is the empty slot wait action, fade out - if (this_ptr == empty_slot_pressed_wait) { - core::api::faderb::fade_in(0.4f); - } - } - - void on_scene_load(core::api::scenes::SceneLoadEventMetadata* metadata) { - if (metadata->scene_name == "wotwTitleScreen" && metadata->state == app::SceneState__Enum::Loaded) { - auto scene_root = metadata->scene->fields.SceneRoot; - auto scene_root_go = il2cpp::unity::get_game_object(scene_root); - - std::vector> game_objects_to_nuke{ - { "titleScreen (new)", "startGameSequence", "02. Set Game Mode To Prologue Action" }, - { "titleScreen (new)", "startGameSequence", "03. Play Sound: Unknown Wise Event" }, - { "titleScreen (new)", "startGameSequence", "05. Play External Timeline Action" }, - { "titleScreen (new)", "ui", "group", "IV. profileSelected", "4. fullGameMainMenu", "emptySlotPressed(newGame)", "03. Play Sound: mainMenuPressNewGameSoundProvider" }, - { "titleScreen (new)", "ui", "group", "IV. profileSelected", "4. fullGameMainMenu", "emptySlotPressed(newGame)", "06. Wait 2 seconds" }, - { "titleScreen (new)", "ui", "group", "actions", "usedSlotPressed (part2)", "05. Wait 1 second"}, - }; - - for (const auto& path : game_objects_to_nuke) { - auto target_go = il2cpp::unity::find_child(scene_root_go, path); - if (il2cpp::unity::is_valid(target_go)) { - il2cpp::unity::destroy_object(target_go); - } - } - - // We shorten the wait time to 0.4s. We use that time to fade to black. - auto empty_slot_pressed_wait_go = il2cpp::unity::find_child(scene_root_go, std::vector{ "titleScreen (new)", "ui", "group", "IV. profileSelected", "4. fullGameMainMenu", "emptySlotPressed(newGame)", "04. Wait 1.5 seconds" }); - empty_slot_pressed_wait = il2cpp::unity::get_component(empty_slot_pressed_wait_go, types::WaitAction::get_class()); - empty_slot_pressed_wait->fields.Duration = 0.4f; - - // Make QTMs faster - auto qtm_fade_to_black_go = il2cpp::unity::find_child(scene_root_go, std::vector{ "titleScreen (new)", "ui", "group", "actions", "usedSlotPressed (part2)", "06. FadeToBlack over 5 seconds"} ); - auto qtm_fade_to_black = il2cpp::unity::get_component(qtm_fade_to_black_go, types::FaderBFadeInAction::get_class()); - qtm_fade_to_black->fields.FadeInDuration = 2.f; - - auto qtm_wait_go = il2cpp::unity::find_child(scene_root_go, std::vector{ "titleScreen (new)", "ui", "group", "actions", "usedSlotPressed (part2)", "07. Wait 5 seconds"} ); - auto qtm_wait = il2cpp::unity::get_component(qtm_wait_go, types::WaitAction::get_class()); - qtm_wait->fields.Duration = 2.f; - } - } - - common::registration_handle on_new_game_late_initialization_handle; - void on_new_game_late_initialization(GameEvent, EventTiming) { - if (!core::api::scenes::is_in_game()) { - return; - } - - core::api::game::event_bus().trigger_event(GameEvent::NewGameInitialized, EventTiming::Before); - core::api::game::event_bus().trigger_event(GameEvent::NewGameInitialized, EventTiming::After); - on_new_game_late_initialization_handle = nullptr; - } - - void on_new_game(GameEvent event, EventTiming timing) { - auto game_state_machine = types::GameStateMachine::get_class()->static_fields->m_instance; - - auto camera = types::UI_Cameras::get_class()->static_fields->Current; - GameplayCamera::DisableGoThroughScrollLocks(camera, reinterpret_cast(game_state_machine)); - ScenesManager::ClearPreventUnloading(core::api::scenes::get_scenes_manager()); - - handling_start = true; - intro_cutscene.set(1); - for (const auto& scene_name : pending_scenes_to_preload) { - core::api::scenes::force_load_scene(scene_name, nullptr, true, false); - } - - teleport(game_seed().info().start_position, true); - on_new_game_late_initialization_handle = - core::api::game::event_bus().register_handler(GameEvent::FixedUpdate, EventTiming::After, on_new_game_late_initialization); - - GameStateMachine::SetToGame(game_state_machine); - core::api::game::player::set_ability(app::AbilityType__Enum::SpiritMagnet, false); - } - - void on_finished_loading_save(GameEvent event, EventTiming timing) { - for (const auto& scene_name : scenes_to_preload) { - core::api::scenes::allow_unload_scene(scene_name); - } - - scenes_to_preload.clear(); - } - - auto on_scene_load_handle = core::api::scenes::event_bus().register_handler(&on_scene_load); - auto on_new_game_handle = core::api::game::event_bus().register_handler(GameEvent::NewGame, EventTiming::After, &on_new_game); - auto on_finished_loading_save_handle = core::api::game::event_bus().register_handler(GameEvent::FinishedLoadingSave, EventTiming::After, &on_finished_loading_save); - } // namespace - - void teleport(app::Vector3 position, bool wait_for_load) { - teleport_state = TeleportState::Teleport; - teleport_position = position; - modloader::trace(MessageType::Info, 3, "teleport", fmt::format("Teleport to ({}, {}, {}) initiated", position.x, position.y, position.z)); - if (wait_for_load) { - ScenesManager::LoadScenesAtPosition(core::api::scenes::get_scenes_manager(), position, false, false, true, true, true); - } - } -} // namespace randomizer::game diff --git a/projects/Randomizer/game/spawning_preloading_teleporting_and_race_lobby_yes_this_file_needs_to_be_split.cpp b/projects/Randomizer/game/spawning_preloading_teleporting_and_race_lobby_yes_this_file_needs_to_be_split.cpp new file mode 100644 index 00000000000..599459d1e1b --- /dev/null +++ b/projects/Randomizer/game/spawning_preloading_teleporting_and_race_lobby_yes_this_file_needs_to_be_split.cpp @@ -0,0 +1,481 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "Modloader/windows_api/windows.h" + +using namespace modloader; +using namespace app::classes; +using modloader::win::console::console_send; + +namespace randomizer::game { + namespace { + enum class TeleportState { + None, + Teleport, + PostTeleport, + }; + + constexpr int MAX_DISPLAYED_WAITING_FOR_PLAYERS = 8; + const app::Vector3 ORIGINAL_START = {-798.797058f, -4310.119141f, 0.f}; + bool handling_start = false; + bool block_starting_new_game = false; + bool is_in_lobby = false; + bool is_starting_game = false; + app::Vector3 start_position = ORIGINAL_START; + TeleportState teleport_state = TeleportState::None; + app::Vector3 teleport_position; + + std::set pending_scenes_to_preload; + std::set scenes_to_preload; + + std::optional> ui_go_handle; + std::optional> start_game_sequence_handle; + std::optional> full_game_main_menu_selection_manager_handle; + std::optional> empty_slot_pressed_action_sequence_handle; + std::optional> easy_mode_text_handle; + std::optional> normal_mode_text_handle; + std::optional> hard_mode_text_handle; + std::shared_ptr lobby_status_text_box; + + void set_full_game_main_menu_selection_manager_active(bool active) { + if (full_game_main_menu_selection_manager_handle.has_value() && full_game_main_menu_selection_manager_handle->is_valid()) { + CleverMenuItemSelectionManager::set_IsActive(**full_game_main_menu_selection_manager_handle, active); + CleverMenuItemSelectionManager::set_IsLocked(**full_game_main_menu_selection_manager_handle, !active); + } + } + + void update_lobby_ui() { + if (is_in_lobby && !is_starting_game) { + if (lobby_status_text_box == nullptr) { + lobby_status_text_box = std::make_shared(); + } + + lobby_status_text_box->alignment() = app::AlignmentMode__Enum::Center; + lobby_status_text_box->horizontal_anchor() = app::HorizontalAnchorMode__Enum::Center; + lobby_status_text_box->vertical_anchor() = app::VerticalAnchorMode__Enum::Top; + lobby_status_text_box->position() = app::Vector3{0.f, 1.5f, 0.f}; + + std::string text; + + if (!pending_scenes_to_preload.empty()) { + text = "Waiting for spawn area to preload..."; + } + else { + text = "Waiting for players:"; + int displayed_waiting_for_players_count = 0; + int total_waiting_for_players_count = 0; + + if (randomizer::multiplayer_universe().multiverse_info().has_value()) { + for (auto universe: randomizer::multiplayer_universe().multiverse_info().value().universes()) { + for (auto world: universe.worlds()) { + for (auto player: world.members()) { + if (!player.raceready()) { + if (displayed_waiting_for_players_count < MAX_DISPLAYED_WAITING_FOR_PLAYERS) { + text += fmt::format("\n{}", player.name()); + + if (!player.has_connectedmultiverseid()) { + text += " (not connected)"; + } + + ++displayed_waiting_for_players_count; + } + + ++total_waiting_for_players_count; + } + } + } + } + } + + if (total_waiting_for_players_count > displayed_waiting_for_players_count) { + text += fmt::format("\n...and {} more", total_waiting_for_players_count - displayed_waiting_for_players_count); + } + + if (total_waiting_for_players_count == 0) { + text = " \n\n"; + } + } + + text += "\n\nPress [MenuBack] to leave"; + + lobby_status_text_box->text() = text; + lobby_status_text_box->show(true, false); + } + else if (lobby_status_text_box != nullptr) { + lobby_status_text_box = nullptr; + } + + if (ui_go_handle.has_value() && ui_go_handle->is_valid()) { + il2cpp::unity::set_active(**ui_go_handle, !is_in_lobby && !is_starting_game); + } + + set_full_game_main_menu_selection_manager_active(!is_in_lobby); + } + + void start_new_game() { + if (start_game_sequence_handle.has_value()) { + auto action = start_game_sequence_handle.value().ref(); + + if (il2cpp::unity::is_valid(action)) { + is_in_lobby = false; + is_starting_game = true; + core::api::faderb::fade_in(0.4f); + core::events::schedule_task(0.4f, [action]() { ActionSequence::Perform_1(action); }); + } + } + } + + void check_if_preloaded_and_report_ready() { + if (pending_scenes_to_preload.empty()) { + randomizer::multiplayer_universe().report_player_ready(true); + } + } + + void update_difficulty_text_boxes() { + std::string prepend_to_difficulty = ""; + + if (block_starting_new_game) { + prepend_to_difficulty = "JOIN RACE in "; + } + + if (easy_mode_text_handle.has_value() && **easy_mode_text_handle != nullptr) { + (**easy_mode_text_handle)->fields.MessageProvider = core::api::system::create_message_provider(fmt::format("{}EASY MODE", prepend_to_difficulty)); + MessageBox::RefreshText_1(**easy_mode_text_handle); + } + + if (normal_mode_text_handle.has_value() && **normal_mode_text_handle != nullptr) { + (**normal_mode_text_handle)->fields.MessageProvider = core::api::system::create_message_provider(fmt::format("{}NORMAL MODE", prepend_to_difficulty)); + MessageBox::RefreshText_1(**normal_mode_text_handle); + } + + if (hard_mode_text_handle.has_value() && **hard_mode_text_handle != nullptr) { + (**hard_mode_text_handle)->fields.MessageProvider = core::api::system::create_message_provider(fmt::format("{}HARD MODE", prepend_to_difficulty)); + MessageBox::RefreshText_1(**hard_mode_text_handle); + } + } + + void on_fixed_update(GameEvent event, EventTiming timing) { + auto menu_back_input = types::Input_Cmd::get_class()->static_fields->MenuBack; + + if (!il2cpp::unity::is_valid(menu_back_input)) { + return; + } + + if (is_in_lobby && Core::Input_InputButtonProcessor::get_OnPressed(menu_back_input)) { + is_in_lobby = false; + randomizer::multiplayer_universe().report_player_ready(false); + update_lobby_ui(); + } + } + + IL2CPP_INTERCEPT(SeinCharacter, void, FixedUpdate, (app::SeinCharacter * this_ptr)) { + // Don't teleport during cutscene skips, causes crashes. + if (teleport_state == TeleportState::Teleport) { + SeinCharacter::set_Position(this_ptr, teleport_position); + teleport_state = TeleportState::PostTeleport; + } + else if (teleport_state == TeleportState::PostTeleport) { + core::api::game::player::snap_camera(); + + ScenesManager::EnableDisabledScenesAtPosition(core::api::scenes::get_scenes_manager(), false, false); + SeinCharacter::set_Position(this_ptr, teleport_position); + + if (handling_start) { + handling_start = false; + core::api::faderb::fade_out(0.3f); + } + + teleport_state = TeleportState::None; + + auto area_map_ui = types::AreaMapUI::get_class()->static_fields->Instance; + auto quests_ui = types::QuestsUI::get_class()->static_fields->Instance; + AreaMapNavigation::SetLocationPlayer(area_map_ui->fields._Navigation_k__BackingField); + QuestsUI::UpdateDescriptionUI_2(quests_ui, nullptr); + } + + next::SeinCharacter::FixedUpdate(this_ptr); + } + + core::api::uber_states::UberState intro_cutscene(static_cast(21786), 48748); + + IL2CPP_INTERCEPT(Moon::uberSerializationWisp::PlayerUberStateAreaMapInformation, void, SetAreaState, + (app::PlayerUberStateAreaMapInformation * this_ptr, app::GameWorldAreaID__Enum area_id, int index, app::WorldMapAreaState__Enum state, app::Vector3 position)) { + if (handling_start && state == app::WorldMapAreaState__Enum::Visited) + state = app::WorldMapAreaState__Enum::Discovered; + + next::Moon::uberSerializationWisp::PlayerUberStateAreaMapInformation::SetAreaState(this_ptr, area_id, index, state, position); + } + + // Dont cancel loads during teleportation. + IL2CPP_INTERCEPT(ScenesManager, bool, CancelScene, (app::ScenesManager * this_ptr, app::SceneManagerScene* scene)) { + if (teleport_state != TeleportState::Teleport) + return next::ScenesManager::CancelScene(this_ptr, scene); + + return false; + } + + // The game calls set_CurrentSlotIndex on startup. We set this variable to true + // for this to not start preloading too early. + bool prevent_preload_on_selecting_empty_save = false; + + // region Preload when selecting empty save slot + app::SaveSlotsUI* get_save_slots_ui() { + auto save_slots_ui_klass = types::SaveSlotsUI::get_class(); + return save_slots_ui_klass->static_fields->Instance; + } + + IL2CPP_INTERCEPT(TitleScreenManager, void, SetScreen, (app::TitleScreenManager_Screen__Enum screen)) { + next::TitleScreenManager::SetScreen(screen); + + if (screen == app::TitleScreenManager_Screen__Enum::SaveSlots) { + auto save_slots_ui = get_save_slots_ui(); + if (save_slots_ui != nullptr) { + auto save_slot_ui = SaveSlotsUI::get_CurrentSaveSlot(save_slots_ui); + SaveSlotUI::SetBusy(save_slot_ui, false); + } + } + } + + void on_scene_loading(core::api::scenes::SceneLoadEventMetadata* metadata) { + if (metadata->state == app::SceneState__Enum::Loaded || metadata->state == app::SceneState__Enum::LoadingCancelled) { + if (!pending_scenes_to_preload.erase(metadata->scene_name.data())) { + return; + } + + if (pending_scenes_to_preload.empty()) { + auto save_slots_ui = get_save_slots_ui(); + + if (save_slots_ui != nullptr) { + auto save_slot_ui = SaveSlotsUI::get_CurrentSaveSlot(save_slots_ui); + SaveSlotUI::SetBusy(save_slot_ui, false); + } + + // We loaded everything, check if we are ready and waiting... + if (is_in_lobby) { + check_if_preloaded_and_report_ready(); + } + } + } + } + + IL2CPP_INTERCEPT(SaveSlotsUI, void, OnEnable, (app::SaveSlotsUI * this_ptr)) { + ScopedSetter setter(prevent_preload_on_selecting_empty_save, true); + next::SaveSlotsUI::OnEnable(this_ptr); + } + + IL2CPP_INTERCEPT(SaveSlotsManager, void, set_CurrentSlotIndex, (int index)) { + next::SaveSlotsManager::set_CurrentSlotIndex(index); + + if (!prevent_preload_on_selecting_empty_save) { + auto slot_info = SaveSlotsManager::SlotByIndex(index); + + if (slot_info == nullptr) { + console_send(fmt::format("Selected empty index {}", index)); + + auto save_slots_ui = get_save_slots_ui(); + + if (save_slots_ui != nullptr) { + auto save_slot_ui = SaveSlotsUI::get_CurrentSaveSlot(save_slots_ui); + + auto scene_names = core::api::scenes::get_scenes_at_position(start_position); + + for (const auto& scene_name: scenes_to_preload) { + if (!scene_names.contains(scene_name)) { + core::api::scenes::unload_scene(scene_name, false); + } + } + + pending_scenes_to_preload.clear(); + for (const auto& scene_name: scene_names) { + if (!core::api::scenes::scene_is_loaded(scene_name)) { + pending_scenes_to_preload.emplace(scene_name); + scenes_to_preload.emplace(scene_name); + core::api::scenes::force_load_scene(scene_name, &on_scene_loading, true, true); + } + } + + if (!pending_scenes_to_preload.empty()) { + SaveSlotUI::SetBusy(save_slot_ui, true); + } + } + } + } + } + // endregion + + IL2CPP_INTERCEPT(RunActionOnce, void, Perform, (app::RunActionOnce * this_ptr, app::IContext* context)) { + // If the player started a new empty save slot... + if (empty_slot_pressed_action_sequence_handle.has_value() && reinterpret_cast(this_ptr->fields.Action) == reinterpret_cast(empty_slot_pressed_action_sequence_handle.value().ref()) && + start_game_sequence_handle.has_value()) { + if (block_starting_new_game) { + is_in_lobby = true; + update_lobby_ui(); + check_if_preloaded_and_report_ready(); + } + else { + set_full_game_main_menu_selection_manager_active(false); + start_new_game(); + } + } + else { + next::RunActionOnce::Perform(this_ptr, context); + } + } + + void on_scene_load(core::api::scenes::SceneLoadEventMetadata* metadata) { + if (metadata->scene_name == "wotwTitleScreen" && metadata->state == app::SceneState__Enum::Loaded) { + is_starting_game = false; + + auto scene_root = metadata->scene->fields.SceneRoot; + auto scene_root_go = il2cpp::unity::get_game_object(scene_root); + + std::vector> game_objects_to_nuke{ + {"titleScreen (new)", "startGameSequence", "02. Set Game Mode To Prologue Action"}, + {"titleScreen (new)", "startGameSequence", "03. Play Sound: Unknown Wise Event"}, + {"titleScreen (new)", "startGameSequence", "05. Play External Timeline Action"}, + {"titleScreen (new)", "ui", "group", "IV. profileSelected", "4. fullGameMainMenu", "emptySlotPressed(newGame)", "01. Activate Menu Action"}, + {"titleScreen (new)", "ui", "group", "IV. profileSelected", "4. fullGameMainMenu", "emptySlotPressed(newGame)", "02. Restart ui BaseAnimator"}, + {"titleScreen (new)", "ui", "group", "IV. profileSelected", "4. fullGameMainMenu", "emptySlotPressed(newGame)", "03. Play Sound: mainMenuPressNewGameSoundProvider"}, + {"titleScreen (new)", "ui", "group", "IV. profileSelected", "4. fullGameMainMenu", "emptySlotPressed(newGame)", "04. Wait 1.5 seconds"}, + {"titleScreen (new)", "ui", "group", "IV. profileSelected", "4. fullGameMainMenu", "emptySlotPressed(newGame)", "05. Run startGameSequence action"}, + {"titleScreen (new)", "ui", "group", "IV. profileSelected", "4. fullGameMainMenu", "emptySlotPressed(newGame)", "06. Wait 2 seconds"}, + {"titleScreen (new)", "ui", "group", "IV. profileSelected", "4. fullGameMainMenu", "emptySlotPressed(newGame)", "07. Deactivate ui"}, + {"titleScreen (new)", "ui", "group", "actions", "usedSlotPressed (part2)", "05. Wait 1 second"}, + }; + + for (const auto& path: game_objects_to_nuke) { + auto target_go = il2cpp::unity::find_child(scene_root_go, path); + if (il2cpp::unity::is_valid(target_go)) { + il2cpp::unity::destroy_object(target_go); + } + } + + // We shorten the wait time to 0.4s. We use that time to fade to black. + auto empty_slot_pressed_action_sequence_go = il2cpp::unity::find_child(scene_root_go, std::vector{"titleScreen (new)", "ui", "group", "IV. profileSelected", "4. fullGameMainMenu", "emptySlotPressed(newGame)"}); + auto empty_slot_pressed_action_sequence = il2cpp::unity::get_component(empty_slot_pressed_action_sequence_go, types::ActionSequence::get_class()); + empty_slot_pressed_action_sequence_handle = il2cpp::WeakGCRef(empty_slot_pressed_action_sequence); + + // Save handles + ui_go_handle = il2cpp::WeakGCRef(il2cpp::unity::find_child(scene_root_go, std::vector{"titleScreen (new)", "ui"})); + auto full_game_main_menu_go_handle = il2cpp::unity::find_child(scene_root_go, std::vector{"titleScreen (new)", "ui", "group", "IV. profileSelected", "4. fullGameMainMenu"}); + full_game_main_menu_selection_manager_handle = il2cpp::WeakGCRef(il2cpp::unity::get_component(full_game_main_menu_go_handle, types::CleverMenuItemSelectionManager::get_class())); + start_game_sequence_handle = + il2cpp::WeakGCRef(il2cpp::unity::get_component(il2cpp::unity::find_child(scene_root_go, std::vector{"titleScreen (new)", "startGameSequence"}), types::ActionSequence::get_class())); + + easy_mode_text_handle = il2cpp::WeakGCRef(il2cpp::unity::get_component( + il2cpp::unity::find_child(scene_root_go, std::vector{"titleScreen (new)", "ui", "group", "IV. profileSelected", "4. fullGameMainMenu", "0. easyMode", "text"}), types::MessageBox::get_class())); + normal_mode_text_handle = il2cpp::WeakGCRef(il2cpp::unity::get_component( + il2cpp::unity::find_child(scene_root_go, std::vector{"titleScreen (new)", "ui", "group", "IV. profileSelected", "4. fullGameMainMenu", "0. normalMode", "text"}), types::MessageBox::get_class())); + hard_mode_text_handle = il2cpp::WeakGCRef(il2cpp::unity::get_component( + il2cpp::unity::find_child(scene_root_go, std::vector{"titleScreen (new)", "ui", "group", "IV. profileSelected", "4. fullGameMainMenu", "0. hardMode", "text"}), types::MessageBox::get_class())); + + // Make QTMs faster + auto qtm_fade_to_black_go = il2cpp::unity::find_child(scene_root_go, std::vector{"titleScreen (new)", "ui", "group", "actions", "usedSlotPressed (part2)", "06. FadeToBlack over 5 seconds"}); + auto qtm_fade_to_black = il2cpp::unity::get_component(qtm_fade_to_black_go, types::FaderBFadeInAction::get_class()); + qtm_fade_to_black->fields.FadeInDuration = 2.f; + + auto qtm_wait_go = il2cpp::unity::find_child(scene_root_go, std::vector{"titleScreen (new)", "ui", "group", "actions", "usedSlotPressed (part2)", "07. Wait 5 seconds"}); + auto qtm_wait = il2cpp::unity::get_component(qtm_wait_go, types::WaitAction::get_class()); + qtm_wait->fields.Duration = 2.f; + + update_difficulty_text_boxes(); + } + } + + common::registration_handle on_new_game_late_initialization_handle; + void on_new_game_late_initialization(GameEvent, EventTiming) { + if (!core::api::scenes::is_in_game()) { + return; + } + + core::api::game::event_bus().trigger_event(GameEvent::NewGameInitialized, EventTiming::Before); + core::api::game::event_bus().trigger_event(GameEvent::NewGameInitialized, EventTiming::After); + on_new_game_late_initialization_handle = nullptr; + } + + void on_new_game(GameEvent event, EventTiming timing) { + auto game_state_machine = types::GameStateMachine::get_class()->static_fields->m_instance; + + auto camera = types::UI_Cameras::get_class()->static_fields->Current; + GameplayCamera::DisableGoThroughScrollLocks(camera, reinterpret_cast(game_state_machine)); + ScenesManager::ClearPreventUnloading(core::api::scenes::get_scenes_manager()); + + handling_start = true; + intro_cutscene.set(1); + for (const auto& scene_name: pending_scenes_to_preload) { + core::api::scenes::force_load_scene(scene_name, nullptr, true, false); + } + + teleport(game_seed().info().start_position, true); + on_new_game_late_initialization_handle = core::api::game::event_bus().register_handler(GameEvent::FixedUpdate, EventTiming::After, on_new_game_late_initialization); + + GameStateMachine::SetToGame(game_state_machine); + core::api::game::player::set_ability(app::AbilityType__Enum::SpiritMagnet, false); + } + + void on_finished_loading_save(GameEvent event, EventTiming timing) { + for (const auto& scene_name: scenes_to_preload) { + core::api::scenes::allow_unload_scene(scene_name); + } + + scenes_to_preload.clear(); + } + + auto _1 = core::api::scenes::event_bus().register_handler(&on_scene_load); + auto _2 = core::api::game::event_bus().register_handler(GameEvent::NewGame, EventTiming::After, &on_new_game); + auto _3 = core::api::game::event_bus().register_handler(GameEvent::FinishedLoadingSave, EventTiming::After, &on_finished_loading_save); + auto _4 = core::api::game::event_bus().register_handler(GameEvent::FixedUpdate, EventTiming::After, &on_fixed_update); + auto _5 = core::api::game::event_bus().register_handler(GameEvent::MultiverseUpdated, EventTiming::After, [](auto, auto) { update_lobby_ui(); }); + } // namespace + + void teleport(app::Vector3 position, bool wait_for_load) { + teleport_state = TeleportState::Teleport; + teleport_position = position; + modloader::trace(MessageType::Info, 3, "teleport", fmt::format("Teleport to ({}, {}, {}) initiated", position.x, position.y, position.z)); + if (wait_for_load) { + ScenesManager::LoadScenesAtPosition(core::api::scenes::get_scenes_manager(), position, false, false, true, true, true); + } + } +} // namespace randomizer::game diff --git a/projects/Randomizer/online/multiplayer.cpp b/projects/Randomizer/online/multiplayer.cpp index 479a9e343af..1830159c26f 100644 --- a/projects/Randomizer/online/multiplayer.cpp +++ b/projects/Randomizer/online/multiplayer.cpp @@ -169,6 +169,12 @@ namespace randomizer::online { return it != m_players.end() ? it->second : std::optional(); } + void MultiplayerUniverse::report_player_ready(const bool ready) const { + Network::ReportPlayerRaceReadyMessage message; + message.set_raceready(ready); + m_client->websocket_send(Network::Packet_PacketID_ReportPlayerRaceReadyMessage, message); + } + Network::UniverseInfo const* find_universe_with_player(Network::MultiverseInfoMessage const& message, std::string_view id) { for (auto const& u : message.universes()) { for (auto const& w : u.worlds()) { @@ -427,4 +433,4 @@ namespace randomizer::online { // void MultiplayerUniverse::player_caught(Network::PlayerCaught const& message) { // // } -} // namespace randomizer::online \ No newline at end of file +} // namespace randomizer::online diff --git a/projects/Randomizer/online/multiplayer.h b/projects/Randomizer/online/multiplayer.h index 1cd3841602b..dbf4b041e00 100644 --- a/projects/Randomizer/online/multiplayer.h +++ b/projects/Randomizer/online/multiplayer.h @@ -35,6 +35,7 @@ namespace randomizer::online { app::Color local_player_color() const { return m_color; } std::optional local_player() const; std::optional multiverse_info() const { return m_last_multiverse_info; } + void report_player_ready(bool ready) const; int player_count() const { return static_cast(m_players.size()); }