From 32ffd7ed097fa21fd75aafe670a6a8767b3c1793 Mon Sep 17 00:00:00 2001 From: Rikhardur Bjarni Einarsson Date: Wed, 22 Nov 2023 14:05:52 +0000 Subject: [PATCH] Added relic handling Message infos now take a DynamicValue for their text entry Reposition the map message Fix some issues with goal interpolation Fix relic strings not being parsed and passed around correctly --- projects/Core/dev/status_display.cpp | 9 +- projects/Core/dynamic_value.h | 2 +- projects/Core/dynamic_value/string.h | 2 +- projects/Core/messages/message_controller.cpp | 2 +- projects/Core/messages/message_display.cpp | 16 +- projects/Core/messages/message_display.h | 4 +- projects/Randomizer/CMakeLists.txt | 2 + .../features/wheel_initialization.cpp | 8 +- .../Randomizer/input/action_registration.cpp | 8 +- .../location_data/location_collection.cpp | 22 ++ .../location_data/location_collection.h | 1 + projects/Randomizer/messages/map_message.cpp | 6 +- projects/Randomizer/randomizer.cpp | 4 +- .../Randomizer/seed/legacy_parser/parser.cpp | 202 ++++++++++-------- projects/Randomizer/seed/relics.cpp | 96 +++++++++ projects/Randomizer/seed/relics.h | 32 +++ projects/Randomizer/seed/seed.cpp | 2 + projects/Randomizer/seed/seed.h | 10 +- .../Randomizer/text_processors/helpers.cpp | 12 ++ projects/Randomizer/text_processors/helpers.h | 1 + projects/Randomizer/text_processors/seed.cpp | 83 ++++++- projects/RandomizerTester/enemy_reporter.cpp | 2 +- 22 files changed, 387 insertions(+), 139 deletions(-) create mode 100644 projects/Randomizer/seed/relics.cpp create mode 100644 projects/Randomizer/seed/relics.h diff --git a/projects/Core/dev/status_display.cpp b/projects/Core/dev/status_display.cpp index b6fb1710c09..efa26eda337 100644 --- a/projects/Core/dev/status_display.cpp +++ b/projects/Core/dev/status_display.cpp @@ -27,17 +27,16 @@ namespace core::dev { void StatusDisplay::report(StatusType type, std::string const& message, float duration) { // Need to hold all the sync handles so we can check what the worst condition is. - auto const& entry = m_config.entries[type]; - messages::MessageInfo info{ - .text = fmt::format(fmt::runtime(entry.format), message), + const auto& [format, size, play_sound] = m_config.entries[type]; + const messages::MessageInfo info{ + .text = fmt::format("{}", size, fmt::format(fmt::runtime(format), message)), .duration = duration, .show_box = false, - .play_sound = entry.play_sound, + .play_sound = play_sound, .margins = m_config.margins, .padding = m_config.padding, }; - info.text = fmt::format("{}", entry.size, info.text); m_display.push(info); } diff --git a/projects/Core/dynamic_value.h b/projects/Core/dynamic_value.h index 65f5aeecad4..65823ab39fe 100644 --- a/projects/Core/dynamic_value.h +++ b/projects/Core/dynamic_value.h @@ -47,7 +47,7 @@ namespace core { template ReadonlyDynamicValue wrap_readonly(dynamic_value::DynamicValue value) { return ReadonlyDynamicValue(typename ReadonlyDynamicValue::value_type( - [&value]() { return static_cast(value.get()); } + [value]() { return static_cast(value.get()); } )); } diff --git a/projects/Core/dynamic_value/string.h b/projects/Core/dynamic_value/string.h index 47c401f3354..1369b360a41 100644 --- a/projects/Core/dynamic_value/string.h +++ b/projects/Core/dynamic_value/string.h @@ -20,7 +20,7 @@ namespace core::dynamic_value { [[nodiscard]] std::string get() const requires(CanGet); - void set(std::string value) + void set(std::string v) requires(CanSet); value_type value; diff --git a/projects/Core/messages/message_controller.cpp b/projects/Core/messages/message_controller.cpp index 690d49aa394..048995f3947 100644 --- a/projects/Core/messages/message_controller.cpp +++ b/projects/Core/messages/message_controller.cpp @@ -136,7 +136,7 @@ namespace core::messages { m_central_display.push(m_saved_message.value()); } else { m_central_display.push({ - .text = "No pickups collected yet, good Luck!", + .text = std::string("No pickups collected yet, good Luck!"), .duration = 5.f, .prioritized = true, }); diff --git a/projects/Core/messages/message_display.cpp b/projects/Core/messages/message_display.cpp index ce238ae2efc..0380cceb0b2 100644 --- a/projects/Core/messages/message_display.cpp +++ b/projects/Core/messages/message_display.cpp @@ -127,7 +127,8 @@ namespace core::messages { const auto max_line_count = m_max_line_count.get(); while (!m_messages.empty()) { auto& data = m_messages.front(); - const auto message_lines = static_cast(std::count(data.info.text.begin(), data.info.text.end(), '\n') + 1); + const auto text = data.info.text.get(); + const auto message_lines = static_cast(std::ranges::count(text, '\n') + 1); // Keep ourselves below the line count limit except if we are showing no messages. // If we get a message that exceeds the line count we still show it. if (max_line_count.has_value() && total_lines != 0 && total_lines + message_lines > max_line_count.value()) { @@ -176,7 +177,8 @@ namespace core::messages { } void MessageDisplay::update_message_position(MessageData& data, int& total_lines, app::Vector3& cursor_position, float delta_time) { - const auto message_lines = static_cast(std::count(data.info.text.begin(), data.info.text.end(), '\n') + 1); + const auto text = data.info.text.get(); + const auto message_lines = static_cast(std::ranges::count(text, '\n') + 1); auto display_message_in_game_world = false; if (data.info.use_world_space) { @@ -193,7 +195,7 @@ namespace core::messages { target_position = world_to_ui_position(data.info.pickup_position.value()); target_position = UnityEngine::Vector3::op_Subtraction( target_position, - core::api::messages::get_screen_position(m_screen_position.get().value_or(core::api::messages::ScreenPosition::TopCenter)) + get_screen_position(m_screen_position.get().value_or(core::api::messages::ScreenPosition::TopCenter)) ); } @@ -211,8 +213,8 @@ namespace core::messages { } // Add message box height and bottom padding/margin - const auto bounds = data.message->text_bounds(); - cursor_position.y += bounds.m_Height; + const auto [m_XMin, m_YMin, m_Width, m_Height] = data.message->text_bounds(); + cursor_position.y += m_Height; cursor_position.y -= data.info.margins.y; cursor_position.y -= data.info.padding.z; @@ -227,8 +229,8 @@ namespace core::messages { ) { data.message = std::make_shared(); data.message->show_box(data.info.show_box); + data.message->text() = data.info.text; data.message->text().text_processor(m_text_processor); - data.message->text().set(data.info.text); data.message->top_padding().set(data.info.padding.x); data.message->left_padding().set(data.info.padding.y); data.message->bottom_padding().set(data.info.padding.z); @@ -240,7 +242,7 @@ namespace core::messages { update_message_position(data, total_lines, position, 0.f); - if (!data.info.text.empty()) { + if (!data.info.text.get().empty()) { data.message->show(false, data.info.play_sound); } diff --git a/projects/Core/messages/message_display.h b/projects/Core/messages/message_display.h index d0a47f38c2f..72485de42e0 100644 --- a/projects/Core/messages/message_display.h +++ b/projects/Core/messages/message_display.h @@ -13,7 +13,7 @@ namespace core::messages { struct MessageInfo { - std::string text; + DynamicValue text; float duration = 3.f; bool show_box = true; bool instant_fade = false; @@ -59,7 +59,7 @@ namespace core::messages { void update_message_queue(int& total_lines, app::Vector3& cursor_position); void update_message_position(MessageData& data, int& total_lines, app::Vector3& cursor_position, float delta_time); bool handle_active_message(MessageData& data, int& total_lines, app::Vector3& cursor_position, float fade_out, float delta_time); - void show_message_box(MessageData& data, int& total_lines, app::Vector3& cursor_position); + void show_message_box(MessageData& data, int& total_lines, app::Vector3& position); // TODO: Implement this. const float m_max_length_from_position = 20.f; diff --git a/projects/Randomizer/CMakeLists.txt b/projects/Randomizer/CMakeLists.txt index ef6f63f02b9..ac9dcdef589 100644 --- a/projects/Randomizer/CMakeLists.txt +++ b/projects/Randomizer/CMakeLists.txt @@ -151,6 +151,7 @@ set( "seed/items/skip_state.cpp" "seed/legacy_parser/parser.cpp" "seed/reach_check.cpp" + "seed/relics.cpp" "seed/seed.cpp" "state_data/parser.cpp" "shrek.cpp" @@ -234,6 +235,7 @@ set( "seed/legacy_parser/parser.h" "seed/item_data.h" "seed/reach_check.h" + "seed/relics.h" "seed/seed.h" "state_data/state.h" "state_data/parser.h" diff --git a/projects/Randomizer/features/wheel_initialization.cpp b/projects/Randomizer/features/wheel_initialization.cpp index 9d8e09f5478..d2276ca0cfa 100644 --- a/projects/Randomizer/features/wheel_initialization.cpp +++ b/projects/Randomizer/features/wheel_initialization.cpp @@ -48,7 +48,7 @@ namespace randomizer::features::wheel { features::credits::start(); } else { core::message_controller().queue_central({ - .text = "Credit warp not unlocked!", + .text = std::string("Credit warp not unlocked!"), .prioritized = true, }); } @@ -120,7 +120,7 @@ namespace randomizer::features::wheel { [](auto, auto, auto) { if (game_seed().info().race_mode) { core::message_controller().queue_central({ - .text = "Teleport anywhere is not available in race mode", + .text = std::string("Teleport anywhere is not available in race mode"), .prioritized = true, }); return; @@ -136,7 +136,7 @@ namespace randomizer::features::wheel { [](auto, auto, auto) { if (game_seed().info().race_mode) { core::message_controller().queue_central({ - .text = "Unlock spoilers is not available in race mode", + .text = std::string("Unlock spoilers is not available in race mode"), .prioritized = true, }); return; @@ -144,7 +144,7 @@ namespace randomizer::features::wheel { core::api::uber_states::UberState(34543, 11226).set(1); core::message_controller().queue_central({ - .text = "Spoilers unlocked", + .text = std::string("Spoilers unlocked"), .prioritized = true, }); }); diff --git a/projects/Randomizer/input/action_registration.cpp b/projects/Randomizer/input/action_registration.cpp index fac00ac045b..e4c4c55a219 100644 --- a/projects/Randomizer/input/action_registration.cpp +++ b/projects/Randomizer/input/action_registration.cpp @@ -49,7 +49,7 @@ namespace randomizer::input { features::credits::start(); } else { core::message_controller().queue_central({ - .text = "Credit warp not unlocked!", + .text = std::string("Credit warp not unlocked!"), .prioritized = true, }); } @@ -98,7 +98,7 @@ namespace randomizer::input { auto on_teleport_cheat_before = single_input_bus().register_handler(Action::TeleportCheat, EventTiming::Before, [](auto, auto) { if (game_seed().info().race_mode) { core::message_controller().queue_central({ - .text = "Teleport anywhere is not available in race mode", + .text = std::string("Teleport anywhere is not available in race mode"), .prioritized = true, }); return; @@ -114,7 +114,7 @@ namespace randomizer::input { auto on_unlock_spoilers_before = single_input_bus().register_handler(Action::UnlockSpoilers, EventTiming::Before, [](auto, auto) { if (game_seed().info().race_mode) { core::message_controller().queue_central({ - .text = "Unlock spoilers is not available in race mode", + .text = std::string("Unlock spoilers is not available in race mode"), .prioritized = true, }); return; @@ -122,7 +122,7 @@ namespace randomizer::input { core::api::uber_states::UberState(34543, 11226).set(1); core::message_controller().queue_central({ - .text = "Spoilers unlocked", + .text = std::string("Spoilers unlocked"), .prioritized = true, }); }); diff --git a/projects/Randomizer/location_data/location_collection.cpp b/projects/Randomizer/location_data/location_collection.cpp index bf808bb0785..ca3fd7208ac 100644 --- a/projects/Randomizer/location_data/location_collection.cpp +++ b/projects/Randomizer/location_data/location_collection.cpp @@ -39,11 +39,33 @@ namespace randomizer::location_data { { "Void", GameArea::Void }, }; + std::unordered_map area_to_short_name_map{ + { GameArea::Marsh, "Marsh" }, + { GameArea::Hollow, "Hollow" }, + { GameArea::Glades, "Glades" }, + { GameArea::Wellspring, "Wellspring" }, + { GameArea::Pools, "Pools" }, + { GameArea::Burrows, "Burrows" }, + { GameArea::Reach, "Reach" }, + { GameArea::Woods, "Woods" }, + { GameArea::Depths, "Depths" }, + { GameArea::Wastes, "Wastes" }, + { GameArea::Ruins, "Ruins" }, + { GameArea::Willow, "Willow" }, + { GameArea::Shop, "Shop" }, + { GameArea::Void, "Void" }, + }; + const std::string_view area_to_name(GameArea area) { auto it = area_to_name_map.find(area); return it != area_to_name_map.end() ? it->second : unknown_area; } + const std::string_view area_to_short_name(GameArea area) { + auto it = area_to_short_name_map.find(area); + return it != area_to_short_name_map.end() ? it->second : unknown_area; + } + GameArea name_to_area(std::string const& name) { auto it = name_to_area_map.find(name); return it != name_to_area_map.end() ? it->second : GameArea::TOTAL; diff --git a/projects/Randomizer/location_data/location_collection.h b/projects/Randomizer/location_data/location_collection.h index acb7c4f5f41..289ff35df12 100644 --- a/projects/Randomizer/location_data/location_collection.h +++ b/projects/Randomizer/location_data/location_collection.h @@ -9,6 +9,7 @@ namespace randomizer::location_data { const std::string_view area_to_name(GameArea area); + const std::string_view area_to_short_name(GameArea area); GameArea name_to_area(std::string const& name); using location_data_emitter = std::function; diff --git a/projects/Randomizer/messages/map_message.cpp b/projects/Randomizer/messages/map_message.cpp index 048500abf35..fc6a7721960 100644 --- a/projects/Randomizer/messages/map_message.cpp +++ b/projects/Randomizer/messages/map_message.cpp @@ -12,13 +12,13 @@ namespace randomizer::messages { if (box == nullptr) { box = std::make_shared(); box->position() = app::Vector3{ - 0, -0.4, 0 + 0, 0.0, 0 }; - box->screen_position() = core::api::messages::ScreenPosition::TopCenter; + box->screen_position() = core::api::messages::ScreenPosition::BottomCenter; box->alignment() = app::AlignmentMode__Enum::Center; box->horizontal_anchor() = app::HorizontalAnchorMode__Enum::Center; - box->vertical_anchor() = app::VerticalAnchorMode__Enum::Top; + box->vertical_anchor() = app::VerticalAnchorMode__Enum::Bottom; box->use_world_coordinates() = false; box->show_box(false); box->text().text_processor(general_text_processor()); diff --git a/projects/Randomizer/randomizer.cpp b/projects/Randomizer/randomizer.cpp index 3893a8742ac..f5e6db5ec53 100644 --- a/projects/Randomizer/randomizer.cpp +++ b/projects/Randomizer/randomizer.cpp @@ -21,6 +21,7 @@ #include #include +#include #include @@ -114,7 +115,7 @@ namespace randomizer { }); core::message_controller().queue_central({ - .text = "*Good Luck! <3*", + .text = std::string("*Good Luck! <3*"), .prioritized = true, }); }); @@ -166,6 +167,7 @@ namespace randomizer { text_processor->compose(std::make_shared()); text_processor->compose(std::make_shared()); text_processor->compose(std::make_shared()); + text_processor->compose(std::make_shared()); text_processor->compose(std::make_shared()); core::message_controller().central_display().text_processor(text_processor); diff --git a/projects/Randomizer/seed/legacy_parser/parser.cpp b/projects/Randomizer/seed/legacy_parser/parser.cpp index 0a44b7b111c..155fa4d1535 100644 --- a/projects/Randomizer/seed/legacy_parser/parser.cpp +++ b/projects/Randomizer/seed/legacy_parser/parser.cpp @@ -46,12 +46,12 @@ namespace randomizer::seed::legacy_parser { int& next_location_id; int& next_procedure_id; bool should_add_to_always_granted; - void add_item(ItemData::item_entry entry) { + void add_item(ItemData::item_entry entry) const { auto& collection = should_add_to_always_granted ? location_data.always_granted_items : location_data.items; - collection[next_location_id++] = entry; + collection[next_location_id++] = std::move(entry); } }; @@ -121,14 +121,14 @@ namespace randomizer::seed::legacy_parser { void set_location(items::Message* message, location_type const& location) { auto const& location_data = current_location_data->location(location); if (location_data.has_value() && location_data.value().position.has_value()) { - const auto position = location_data.value().position.value(); - message->info.pickup_position = app::Vector3{ position.x, position.y, 0 }; + const auto [x, y] = location_data.value().position.value(); + message->info.pickup_position = app::Vector3{ x, y, 0 }; } } bool parse_action(location_type const& location, std::span parts, ParserData& data); - void parse_parts(std::string_view str, std::vector& parts) { + void parse_parts(const std::string_view str, std::vector& parts) { enum class ReadMode { Normal, Ptr @@ -168,7 +168,7 @@ namespace randomizer::seed::legacy_parser { return false; } - auto assigner = std::make_shared>(); + const auto assigner = std::make_shared>(); assigner->variable = core::api::game::player::spirit_light(); assigner->value.set(spirit_light); data.add_item(assigner); @@ -181,13 +181,13 @@ namespace randomizer::seed::legacy_parser { } const auto text = fmt::format("{} {}", spirit_light, currency); - auto message = std::make_shared(); + const auto message = std::make_shared(); set_location(message.get(), location); message->should_save_as_last = true; message->info.text = text; data.add_item(message); - data.location_data.names.emplace_back().assign(message->info.text); + data.location_data.names.emplace_back() = core::wrap_readonly(message->info.text); data.location_data.icons.emplace_back().assign(app::WorldMapIconType__Enum::Experience); return true; @@ -203,25 +203,24 @@ namespace randomizer::seed::legacy_parser { return false; } - auto message = std::make_shared(); + const auto message = std::make_shared(); set_location(message.get(), location); message->should_save_as_last = true; - const auto resource_type = static_cast(resource_type_int); - switch (resource_type) { + switch (static_cast(resource_type_int)) { case ResourceType::Health: { - auto adder = std::make_shared>(); + const auto adder = std::make_shared>(); adder->variable = core::api::game::player::max_health(); adder->value.set(5); data.add_item(adder); - auto refill = std::make_shared(); + const auto refill = std::make_shared(); refill->type = items::Refill::RefillType::Health; data.add_item(refill); data.location_data.icons.emplace_back().assign(app::WorldMapIconType__Enum::HealthFragment); - message->info.text = "Health Fragment"; + message->info.text = std::string("Health Fragment"); break; } case ResourceType::Energy: { - auto adder = std::make_shared>(); + const auto adder = std::make_shared>(); adder->variable = core::api::game::player::max_energy(); adder->value.set(0.5f); data.add_item(adder); @@ -229,34 +228,34 @@ namespace randomizer::seed::legacy_parser { refill->type = items::Refill::RefillType::Energy; data.add_item(refill); data.location_data.icons.emplace_back().assign(app::WorldMapIconType__Enum::EnergyFragment); - message->info.text = "Energy Fragment"; + message->info.text = std::string("Energy Fragment"); break; } case ResourceType::Ore: { - auto adder = std::make_shared>(); + const auto adder = std::make_shared>(); adder->variable = core::api::game::player::ore(); adder->value.set(1); data.add_item(adder); data.location_data.icons.emplace_back().assign(app::WorldMapIconType__Enum::Ore); - message->info.text = "Gorlek Ore"; + message->info.text = std::string("Gorlek Ore"); break; } case ResourceType::Keystone: { - auto adder = std::make_shared>(); + const auto adder = std::make_shared>(); adder->variable = core::api::game::player::keystones(); adder->value.set(1); data.add_item(adder); data.location_data.icons.emplace_back().assign(app::WorldMapIconType__Enum::Keystone); - message->info.text = "Keystone"; + message->info.text = std::string("Keystone"); break; } case ResourceType::ShardSlot: { - auto adder = std::make_shared>(); + const auto adder = std::make_shared>(); adder->variable = core::api::game::player::shard_slots(); adder->value.set(1); data.add_item(adder); data.location_data.icons.emplace_back().assign(app::WorldMapIconType__Enum::ShardSlotUpgrade); - message->info.text = "Shard Slot"; + message->info.text = std::string("Shard Slot"); break; } default: @@ -279,11 +278,11 @@ namespace randomizer::seed::legacy_parser { const auto should_add = !parts[0].starts_with("-"); ability_type_int = should_add ? ability_type_int : -ability_type_int; - auto assigner = std::make_shared>(); + const auto assigner = std::make_shared>(); assigner->variable.assign(core::api::uber_states::UberState(6, 1000 + ability_type_int)); assigner->value.set(should_add); data.add_item(assigner); - auto message = std::make_shared(); + const auto message = std::make_shared(); set_location(message.get(), location); message->should_save_as_last = true; const auto text = should_add @@ -309,12 +308,12 @@ namespace randomizer::seed::legacy_parser { const auto should_add = !parts[0].starts_with("-"); shard_type_int = should_add ? shard_type_int : -shard_type_int; const auto shard_type = static_cast(shard_type_int); - auto assigner = std::make_shared>(); + const auto assigner = std::make_shared>(); assigner->variable = core::api::game::player::shard(shard_type); assigner->value.set(should_add); data.add_item(assigner); - auto message = std::make_shared(); + const auto message = std::make_shared(); set_location(message.get(), location); message->should_save_as_last = true; const auto text = should_add @@ -335,7 +334,7 @@ namespace randomizer::seed::legacy_parser { return false; } - auto stop = std::make_shared(); + const auto stop = std::make_shared(); stop->stop.assign(core::api::uber_states::UberStateCondition{ { group, state }, op, value }); data.add_item(stop); return true; @@ -390,7 +389,7 @@ namespace randomizer::seed::legacy_parser { return false; } - auto sub_parts = std::span(parts.begin() + 2, parts.end()); + const auto sub_parts = std::span(parts.begin() + 2, parts.end()); return parse_grant_if_state(location, { group, state }, sub_parts, data, op); } @@ -424,11 +423,11 @@ namespace randomizer::seed::legacy_parser { rect.m_Width -= rect.m_XMin; rect.m_Height -= rect.m_YMin; - auto skip = std::make_shared(); + const auto skip = std::make_shared(); data.add_item(skip); auto skip_value = data.next_location_id; - auto sub_parts = std::span(parts.begin() + 1, parts.end()); + const auto sub_parts = std::span(parts.begin() + 1, parts.end()); parse_action(location, sub_parts, data); skip_value = data.next_location_id - skip_value; @@ -441,7 +440,7 @@ namespace randomizer::seed::legacy_parser { } bool parse_save(ParserData& data, bool checkpoint) { - auto caller = std::make_shared(); + const auto caller = std::make_shared(); if (checkpoint) { caller->func = []() { if (core::api::game::can_save()) { @@ -470,7 +469,7 @@ namespace randomizer::seed::legacy_parser { return false; } - auto caller = std::make_shared(); + const auto caller = std::make_shared(); caller->func = [position]() { game::teleport(position, true); }; data.add_item(caller); return true; @@ -488,7 +487,7 @@ namespace randomizer::seed::legacy_parser { } core::api::uber_states::UberState timer_state(group, state); - auto caller = std::make_shared(); + const auto caller = std::make_shared(); caller->func = [timer_state, start]() { timer::uber_state_timer(timer_state, start); }; data.add_item(caller); return true; @@ -506,7 +505,7 @@ namespace randomizer::seed::legacy_parser { } key.first = parts[0]; - auto caller = std::make_shared(); + const auto caller = std::make_shared(); caller->func = [key, state]() { conditions::register_new_setup_redirect(key, state); }; data.add_item(caller); return true; @@ -533,7 +532,7 @@ namespace randomizer::seed::legacy_parser { return false; } - auto caller = std::make_shared(); + const auto caller = std::make_shared(); caller->func = [binding_type, equip_type]() { core::api::game::player::bind(binding_type, equip_type); }; data.add_item(caller); return true; @@ -554,7 +553,7 @@ namespace randomizer::seed::legacy_parser { return false; } - auto caller = std::make_shared(); + const auto caller = std::make_shared(); caller->func = [equip_type]() { core::api::game::player::unbind(equip_type); }; data.add_item(caller); return true; @@ -572,7 +571,7 @@ namespace randomizer::seed::legacy_parser { } core::api::uber_states::UberState sync_state(group, state); - auto caller = std::make_shared(); + const auto caller = std::make_shared(); caller->func = [sync_state, unsyncable]() { multiplayer_universe().uber_state_handler().set_unsyncable(sync_state, unsyncable); }; data.add_item(caller); return true; @@ -583,7 +582,7 @@ namespace randomizer::seed::legacy_parser { return false; } - auto icon = std::make_shared(); + const auto icon = std::make_shared(); if (!string_convert(parts[0], icon->id) || !string_convert(parts[1], icon->position.x) || !string_convert(parts[2], icon->position.y)) { return false; } @@ -824,7 +823,7 @@ namespace randomizer::seed::legacy_parser { } data.add_item(assigner); - auto message = std::make_shared(); + const auto message = std::make_shared(); set_location(message.get(), location); message->should_save_as_last = true; const auto text = should_add @@ -838,14 +837,14 @@ namespace randomizer::seed::legacy_parser { } bool parse_message(location_type const& location, std::span parts, ParserData& data) { - auto message = std::make_shared(); + const auto message = std::make_shared(); set_location(message.get(), location); message->should_save_as_last = true; message->info.duration = 4; + std::string text; for (const auto& part : parts) { if (part.starts_with("f=")) { - int frames; - if (string_convert(part.substr(2), frames)) { + if (int frames; string_convert(part.substr(2), frames)) { message->info.duration = static_cast(frames) / 60.f; } } else if (part.starts_with("p=")) { @@ -861,16 +860,17 @@ namespace randomizer::seed::legacy_parser { } else if (part == "prepend") { // Not used anymore. } else { - if (!message->info.text.empty()) { - message->info.text += "|"; + if (!text.empty()) { + text += "|"; } - message->info.text += part; + text += part; } } + message->info.text = text; data.add_item(message); - data.location_data.names.emplace_back().assign(message->info.text); + data.location_data.names.emplace_back() = core::wrap_readonly(message->info.text); return true; } @@ -992,15 +992,14 @@ namespace randomizer::seed::legacy_parser { return false; } - int quest_event_int; // If we add more than one event, don't early out here. - if (!string_convert(parts[0], quest_event_int) || quest_event_int != 0) { + if (int quest_event_int; !string_convert(parts[0], quest_event_int) || quest_event_int != 0) { return false; } const auto should_add = !parts[0].starts_with("-"); - auto assigner = std::make_shared>(); + const auto assigner = std::make_shared>(); assigner->value.set(should_add); assigner->variable.assign(core::api::uber_states::UberState(6, 2000)); data.add_item(assigner); @@ -1022,7 +1021,7 @@ namespace randomizer::seed::legacy_parser { ? fmt::format("{0}", quest_event) : fmt::format("Removed {0}", quest_event); - auto message = std::make_shared(); + const auto message = std::make_shared(); set_location(message.get(), location); message->should_save_as_last = true; message->info.text = fmt::format("*{0}*", text); @@ -1062,12 +1061,12 @@ namespace randomizer::seed::legacy_parser { return false; } - auto item = std::make_shared>(); + const auto item = std::make_shared>(); item->variable.assign(core::api::uber_states::UberState(4, bonus_type_int)); item->value.set(1); data.add_item(item); - auto message = std::make_shared(); + const auto message = std::make_shared(); set_location(message.get(), location); message->should_save_as_last = true; message->info.text = fmt::format(R"(#{0}[if([state_int(4, {1})] > 1, x[state_int(4, {1})],)]#)", bonus_item, bonus_type_int); @@ -1143,8 +1142,7 @@ namespace randomizer::seed::legacy_parser { return false; } - auto message = std::make_shared(); - set_location(message.get(), location); + const auto message = std::make_shared(); message->should_save_as_last = true; message->info.text = fmt::format("#{0}#", upgrade); set_location(message.get(), location); @@ -1156,13 +1154,29 @@ namespace randomizer::seed::legacy_parser { return true; } - bool parse_relic(std::span parts, ParserData& data) { - // return Relic.Build((ZoneType)pickupData.ParseToByte("BuildPickup.RelicZone"), extras.FirstOrElse("")); - // TODO: Implement - return false; + bool parse_relic(location_type const& location, std::span parts, ParserData& data) { + if (parts.size() > 2) { + return false; + } + + data.data.relics.add(location, parts.size() == 2 ? std::optional(parts[1]) : std::nullopt); + const auto message = std::make_shared(); + message->should_save_as_last = true; + message->info.text.assign(core::dynamic_value::set_get{ + [](auto) {}, + [&data, location]() { return std::format("{} Relic", std::string(data.data.relics.relic_name(location))); } + }); + + set_location(message.get(), location); + data.add_item(message); + data.location_data.names.emplace_back(core::dynamic_value::get{ + [&data, location]() { return std::string(data.data.relics.relic_name(location)); } + }); + + return true; } - bool parse_sys_message(std::span parts, ParserData& data) { + bool parse_sys_message(const std::span parts, ParserData& data) { int sys_message_type; if (!string_convert(parts[0], sys_message_type)) { return false; @@ -1206,11 +1220,11 @@ namespace randomizer::seed::legacy_parser { // return " (Relicless)"; return false; case 2: { - auto message = std::make_shared(); + const auto message = std::make_shared(); message->should_save_as_last = true; - message->info.text = "[state_int(6, 2)]/[seed(pickup_count)]"; + message->info.text = std::string("[state_int(6, 2)]/[seed(pickup_count)]"); data.add_item(message); - data.location_data.names.emplace_back().assign(message->info.text); + data.location_data.names.emplace_back() = core::wrap_readonly(message->info.text); break; } case 3: @@ -1270,11 +1284,11 @@ namespace randomizer::seed::legacy_parser { } case 5: { // WorldName - auto message = std::make_shared(); - message->info.text = "[world(name)]"; + const auto message = std::make_shared(); + message->info.text = std::string("[world(name)]"); message->should_save_as_last = true; data.add_item(message); - data.location_data.names.emplace_back().assign(message->info.text); + data.location_data.names.emplace_back() = core::wrap_readonly(message->info.text); break; } default: @@ -1295,7 +1309,7 @@ namespace randomizer::seed::legacy_parser { return false; } - auto caller = std::make_shared(); + const auto caller = std::make_shared(); auto& name = parts[2]; caller->func = [wheel, item, name]() { features::wheel::set_wheel_item_name(wheel, item, name); @@ -1316,7 +1330,7 @@ namespace randomizer::seed::legacy_parser { return false; } - auto caller = std::make_shared(); + const auto caller = std::make_shared(); auto& description = parts[2]; caller->func = [wheel, item, description]() { features::wheel::set_wheel_item_description(wheel, item, description); @@ -1337,7 +1351,7 @@ namespace randomizer::seed::legacy_parser { return false; } - auto caller = std::make_shared(); + const auto caller = std::make_shared(); auto& texture = parts[2]; caller->func = [wheel, item, texture]() { features::wheel::set_wheel_item_texture(wheel, item, texture); @@ -1366,7 +1380,7 @@ namespace randomizer::seed::legacy_parser { return false; } - auto caller = std::make_shared(); + const auto caller = std::make_shared(); caller->func = [wheel, item, color]() { features::wheel::set_wheel_item_color( wheel, @@ -1412,7 +1426,7 @@ namespace randomizer::seed::legacy_parser { return false; } - auto caller = std::make_shared(); + const auto caller = std::make_shared(); caller->func = [wheel, item, binding_type, procedure_id]() { features::wheel::set_wheel_item_callback(wheel, item, binding_type, [procedure_id](auto, auto, auto) { game_seed().call_procedure(procedure_id); @@ -1435,7 +1449,7 @@ namespace randomizer::seed::legacy_parser { return false; } - auto caller = std::make_shared(); + const auto caller = std::make_shared(); caller->func = [wheel, sticky]() { features::wheel::set_wheel_sticky(wheel, sticky); }; @@ -1454,7 +1468,7 @@ namespace randomizer::seed::legacy_parser { return false; } - auto caller = std::make_shared(); + const auto caller = std::make_shared(); caller->func = [wheel]() { features::wheel::set_active_wheel(wheel); }; @@ -1474,7 +1488,7 @@ namespace randomizer::seed::legacy_parser { return false; } - auto caller = std::make_shared(); + const auto caller = std::make_shared(); caller->func = [wheel, item]() { features::wheel::clear_wheel_item(wheel, item); }; @@ -1488,7 +1502,7 @@ namespace randomizer::seed::legacy_parser { return false; } - auto caller = std::make_shared(); + const auto caller = std::make_shared(); caller->func = []() { features::wheel::clear_wheels(); }; @@ -1507,7 +1521,7 @@ namespace randomizer::seed::legacy_parser { return false; } - auto next_parts = std::span(parts.begin() + 1, parts.end()); + const auto next_parts = std::span(parts.begin() + 1, parts.end()); switch (wheel_type) { case 0: return parse_wheel_name(next_parts, data); @@ -1547,10 +1561,9 @@ namespace randomizer::seed::legacy_parser { auto icon = parts[2]; auto shop_state = core::api::uber_states::UberState(group, state); - auto caller = std::make_shared(); + const auto caller = std::make_shared(); caller->func = [shop_state, icon]() { - auto slot = game::shops::shop_slot_from_state(shop_state); - if (slot != nullptr) { + if (const auto slot = game::shops::shop_slot_from_state(shop_state); slot != nullptr) { slot->normal.icon = core::api::graphics::textures::get_texture(icon); slot->locked.icon = slot->normal.icon; } @@ -1573,10 +1586,9 @@ namespace randomizer::seed::legacy_parser { auto title = parts.size() == 3 ? parts[2] : " "; auto shop_state = core::api::uber_states::UberState(group, state); - auto caller = std::make_shared(); + const auto caller = std::make_shared(); caller->func = [shop_state, title]() { - auto slot = game::shops::shop_slot_from_state(shop_state); - if (slot != nullptr) { + if (const auto slot = game::shops::shop_slot_from_state(shop_state); slot != nullptr) { slot->normal.name = title; } }; @@ -1598,10 +1610,9 @@ namespace randomizer::seed::legacy_parser { auto description = parts.size() == 3 ? parts[2] : " "; auto shop_state = core::api::uber_states::UberState(group, state); - auto caller = std::make_shared(); + const auto caller = std::make_shared(); caller->func = [shop_state, description]() { - auto slot = game::shops::shop_slot_from_state(shop_state); - if (slot != nullptr) { + if (const auto slot = game::shops::shop_slot_from_state(shop_state); slot != nullptr) { slot->normal.description = description; } }; @@ -1623,10 +1634,9 @@ namespace randomizer::seed::legacy_parser { } auto shop_state = core::api::uber_states::UberState(group, state); - auto caller = std::make_shared(); + const auto caller = std::make_shared(); caller->func = [shop_state, locked]() { - auto slot = game::shops::shop_slot_from_state(shop_state); - if (slot != nullptr && slot->visibility != game::shops::SlotVisibility::Hidden) { + if (const auto slot = game::shops::shop_slot_from_state(shop_state); slot != nullptr && slot->visibility != game::shops::SlotVisibility::Hidden) { slot->visibility = locked ? game::shops::SlotVisibility::Locked : game::shops::SlotVisibility::Visible; @@ -1650,10 +1660,9 @@ namespace randomizer::seed::legacy_parser { } auto shop_state = core::api::uber_states::UberState(group, state); - auto caller = std::make_shared(); + const auto caller = std::make_shared(); caller->func = [shop_state, visible]() { - auto slot = game::shops::shop_slot_from_state(shop_state); - if (slot != nullptr) { + if (const auto slot = game::shops::shop_slot_from_state(shop_state); slot != nullptr) { slot->visibility = visible ? game::shops::SlotVisibility::Visible : game::shops::SlotVisibility::Hidden; @@ -1712,8 +1721,8 @@ namespace randomizer::seed::legacy_parser { return false; } - auto action_type = static_cast(action_type_int); - auto next_parts = std::span(parts.begin() + 1, parts.end()); + const auto action_type = static_cast(action_type_int); + const auto next_parts = std::span(parts.begin() + 1, parts.end()); switch (action_type) { case ActionType::SpiritLight: return parse_spirit_light(location, next_parts, data); @@ -1738,7 +1747,7 @@ namespace randomizer::seed::legacy_parser { case ActionType::WeaponUpgrade: return parse_weapon_upgrade(location, next_parts, data); case ActionType::Relic: - return parse_relic(next_parts, data); + return parse_relic(location, next_parts, data); case ActionType::SysMessage: return parse_sys_message(next_parts, data); case ActionType::Wheel: @@ -1819,6 +1828,9 @@ namespace randomizer::seed::legacy_parser { line = line.substr(0, line.find("//")); if (line.starts_with("Flags:")) { split_str(line.substr(6), data.info.flags, ','); + for (auto& flag : data.info.flags) { + trim(flag); + } } else if (line.starts_with("Spawn:")) { std::vector coords; split_str(line.substr(6), coords, ','); @@ -1844,9 +1856,9 @@ namespace randomizer::seed::legacy_parser { return true; } - ItemData parse_action(std::string_view action) { + ItemData parse_action(const std::string_view action) { Seed::Data data; - location_type location{ + const location_type location{ { -1, -1 }, BooleanOperator::Equals, 0, diff --git a/projects/Randomizer/seed/relics.cpp b/projects/Randomizer/seed/relics.cpp new file mode 100644 index 00000000000..a35b16b2554 --- /dev/null +++ b/projects/Randomizer/seed/relics.cpp @@ -0,0 +1,96 @@ +#include +#include + +namespace randomizer::seed { + void Relics::add(relic_location const& location, const std::optional& flavor_name) { + const auto area = location_collection().area(location); + ++m_relic_count; + m_relics[area][location] = flavor_name.has_value() + ? flavor_name.value() + : location_data::area_to_short_name(area); + } + + void Relics::clear() { + m_relics.clear(); + m_relic_count = 0; + } + + std::string Relics::relic_name(relic_location const& location) const { + const auto area = location_collection().area(location); + const auto relic_area = m_relics.find(area); + if (relic_area == m_relics.end()) { + return "Unknown"; + } + + const auto relic = relic_area->second.find(location); + return relic == relic_area->second.end() ? "Unknown" : relic->second; + } + + std::vector Relics::relics() const { + std::vector output; + for (auto relics : m_relics | std::views::values) { + for (const auto relic : relics | std::views::keys) { + output.push_back(relic); + } + } + + return output; + } + + std::vector Relics::relics_in_area(const GameArea area) const { + std::vector output; + const auto relics = m_relics.find(area); + if (relics == m_relics.end()) { + return output; + } + + for (const auto relic : relics->second | std::views::keys) { + output.push_back(relic); + } + + return output; + } + + int Relics::found_relics() const { + int found = 0; + for (auto relics : m_relics | std::views::values) { + for (const auto relic : relics | std::views::keys) { + if (relic.resolve()) { + ++found; + } + } + } + + return found; + } + + int Relics::found_relics_in_area(const GameArea area) const { + int found = 0; + const auto relics = m_relics.find(area); + if (relics == m_relics.end()) { + return found; + } + + for (const auto relic : relics->second | std::views::keys) { + if (relic.resolve()) { + ++found; + } + } + + return found; + } + + int Relics::relic_count() const { + int count = 0; + for (auto relics : m_relics | std::views::values) { + count += relics.size(); + } + + return count; + } + + int Relics::relic_count_in_area(const GameArea area) const { + const auto relics = m_relics.find(area); + return relics == m_relics.end() ? 0 : relics->second.size(); + } +} diff --git a/projects/Randomizer/seed/relics.h b/projects/Randomizer/seed/relics.h new file mode 100644 index 00000000000..24a1bb3cc85 --- /dev/null +++ b/projects/Randomizer/seed/relics.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +#include +#include +#include + +namespace randomizer::seed { + class Relics { + public: + using relic_location = core::api::uber_states::UberStateCondition; + + void add(relic_location const& location, const std::optional& flavor_name); + void clear(); + + std::string relic_name(relic_location const& location) const; + + std::vector relics() const; + std::vector relics_in_area(GameArea area) const; + + int found_relics() const; + int found_relics_in_area(GameArea area) const; + + int relic_count() const; + int relic_count_in_area(GameArea area) const; + private: + int m_relic_count = 0; + std::unordered_map> m_relics; + }; +} diff --git a/projects/Randomizer/seed/seed.cpp b/projects/Randomizer/seed/seed.cpp index 517ba5d1d9c..310cbb4dc93 100644 --- a/projects/Randomizer/seed/seed.cpp +++ b/projects/Randomizer/seed/seed.cpp @@ -74,7 +74,9 @@ namespace randomizer::seed { void Seed::clear() { m_data.info = {}; + m_data.relics.clear(); m_data.locations.clear(); + m_data.procedures.clear(); } app::WorldMapIconType__Enum Seed::icon(inner_location_entry location) { diff --git a/projects/Randomizer/seed/seed.h b/projects/Randomizer/seed/seed.h index 2f29d34a028..02af27425e1 100644 --- a/projects/Randomizer/seed/seed.h +++ b/projects/Randomizer/seed/seed.h @@ -4,15 +4,10 @@ #include #include -#include -#include - #include #include -#include +#include -#include -#include #include #include @@ -45,6 +40,7 @@ namespace randomizer::seed { struct Data { SeedInfo info; + Relics relics; std::unordered_map locations; std::unordered_map procedures; }; @@ -69,6 +65,8 @@ namespace randomizer::seed { void prevent_grants(bool value) { m_should_prevent_grants = value; } std::string path() const { return m_last_path; } + Relics const& relics() const { return m_data.relics; } + private: location_data::LocationCollection const& m_location_data; seed_parser m_last_parser = nullptr; diff --git a/projects/Randomizer/text_processors/helpers.cpp b/projects/Randomizer/text_processors/helpers.cpp index b4e3909b229..0a706863c6d 100644 --- a/projects/Randomizer/text_processors/helpers.cpp +++ b/projects/Randomizer/text_processors/helpers.cpp @@ -22,4 +22,16 @@ namespace randomizer::text_processors { start = text.find(pattern, next); } } + + void search_and_replace_full(core::text::ITextProcessor const& base_processor, std::string_view pattern, replacer func, std::string& text) { + for (auto i = text.find(pattern); i != std::wstring_view::npos; i = text.find(pattern, i + 1)) { + if (auto replacement = func(base_processor, {}); replacement.has_value()) { + text.replace( + text.begin() + i, + text.begin() + i + pattern.size(), + replacement.value() + ); + } + } + } } // namespace randomizer::text_processors diff --git a/projects/Randomizer/text_processors/helpers.h b/projects/Randomizer/text_processors/helpers.h index 6feceff713e..2f889223a42 100644 --- a/projects/Randomizer/text_processors/helpers.h +++ b/projects/Randomizer/text_processors/helpers.h @@ -9,4 +9,5 @@ namespace randomizer::text_processors { using replacer = std::optional (*)(core::text::ITextProcessor const& base_processor, std::string_view content); void search_and_replace(core::text::ITextProcessor const& base_processor, std::string_view pattern, replacer func, std::string& text, std::string_view begin = "[", std::string_view end = ")]"); + void search_and_replace_full(core::text::ITextProcessor const& base_processor, std::string_view pattern, replacer func, std::string& text); } // namespace randomizer::text_processors diff --git a/projects/Randomizer/text_processors/seed.cpp b/projects/Randomizer/text_processors/seed.cpp index 9ca0cfcd9de..e48838d337a 100644 --- a/projects/Randomizer/text_processors/seed.cpp +++ b/projects/Randomizer/text_processors/seed.cpp @@ -1,31 +1,98 @@ +#include #include #include #include +#include namespace randomizer::text_processors { namespace { std::optional total_pickup_count(core::text::ITextProcessor const& base_processor, std::string_view content) { - return std::nullopt; + return std::to_string(game_seed().info().total_pickups); } std::optional goal_mode_progress(core::text::ITextProcessor const& base_processor, std::string_view content) { - return std::nullopt; + std::vector goals; + auto const& flags = game_seed().info().flags; + if (std::ranges::find(flags, "All Trees") != flags.end()) { + goals.push_back(std::format("Trees: {}/14", core::api::uber_states::UberState(UberStateGroup::RandoVirtual, 502).get())); + } + + if (std::ranges::find(flags, "All Wisps") != flags.end()) { + goals.push_back(std::format("Wisps: {}/5", core::api::uber_states::UberState(UberStateGroup::RandoVirtual, 503).get())); + } + + if (std::ranges::find(flags, "All Quests") != flags.end()) { + goals.push_back(std::format("Quests: {}/17", core::api::uber_states::UberState(UberStateGroup::RandoVirtual, 504).get())); + } + + auto output = std::accumulate(goals.begin(), goals.end(), std::string(), [](auto const& ss, auto const& s) -> decltype(auto) { + return ss.empty() ? s : ss + ", " + s; + }); + + return output.empty() ? output : output + "\n"; } std::optional relic_progress(core::text::ITextProcessor const& base_processor, std::string_view content) { - return std::nullopt; + if (auto const& flags = game_seed().info().flags; std::ranges::find(flags, "Relics") == flags.end()) { + return ""; + } + + auto const& relics = game_seed().relics(); + const auto current_area = core::api::game::player::get_current_area(); + std::string output = std::format("Relics ({}/{}): ", relics.found_relics(), relics.relic_count()); + std::vector> relic_text; + for (auto value : relics.relics()) { + const auto area = location_collection().area(value); + relic_text.emplace_back( + relics.relic_name(value), + value.resolve() ? "$" + : area == current_area ? "#" + : "" + ); + } + + bool start = true; + std::ranges::sort(relic_text); + for (auto [relic, color] : relic_text) { + if (!start) { + output += ", "; + } else { + start = false; + } + + output += std::format("{1}{0}{1}", relic, color); + } + + return output; } std::optional map_relic_progress(core::text::ITextProcessor const& base_processor, std::string_view content) { - return std::nullopt; + if (auto const& flags = game_seed().info().flags; std::ranges::find(flags, "Relics") == flags.end()) { + return ""; + } + + auto const& relics = game_seed().relics(); + const auto current_area = core::api::game::player::get_current_area(); + const auto relics_in_area = relics.relics_in_area(current_area); + const auto total = relics_in_area.size(); + if (total == 0) { + return "(Relicless)"; + } + + if (total == 1) { + return relics_in_area[0].resolve() ? "$(Relic found)$" : "(Relic not found)"; + } + + const auto found = relics.found_relics_in_area(current_area); + return std::format("{2}{0}/{1} Relics found{2}", found, total, found == total ? "$" : ""); } } // namespace void SeedProcessor::process(ITextProcessor const& base_processor, std::string& text) const { - search_and_replace(base_processor, "[total_pickup_count(", total_pickup_count, text); - search_and_replace(base_processor, "[goal_mode_progress(", goal_mode_progress, text); - search_and_replace(base_processor, "[relic_progress(", relic_progress, text); - search_and_replace(base_processor, "[map_relic_progress(", map_relic_progress, text); + search_and_replace_full(base_processor, "[total_pickup_count()]", total_pickup_count, text); + search_and_replace_full(base_processor, "[goal_mode_progress()]", goal_mode_progress, text); + search_and_replace_full(base_processor, "[relic_progress()]", relic_progress, text); + search_and_replace_full(base_processor, "[map_relic_progress()]", map_relic_progress, text); } } // namespace randomizer::text_processors diff --git a/projects/RandomizerTester/enemy_reporter.cpp b/projects/RandomizerTester/enemy_reporter.cpp index 91157fb02ac..71c12499e38 100644 --- a/projects/RandomizerTester/enemy_reporter.cpp +++ b/projects/RandomizerTester/enemy_reporter.cpp @@ -22,7 +22,7 @@ namespace randomizer_tester { core::messages::MessageInfo info; info.text = fmt::format("Enemy died: {}", enemy_entity->klass->_0.name); core::message_controller().queue_central(info); - modloader::trace(modloader::MessageType::Info, 4, "death_listener", info.text); + modloader::trace(modloader::MessageType::Info, 4, "death_listener", info.text.get()); } auto enemy_stats_handle = core::api::death_listener::enemy_death_event_bus().register_handler(EventTiming::Before, handle_enemy_stats);