From b13d3678a9b09ba5dd8a58a7ab441d1e09ee5b77 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Wed, 31 Jan 2024 15:20:57 -0500 Subject: Added sunwarp access support --- src/ap_state.cpp | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/ap_state.cpp') diff --git a/src/ap_state.cpp b/src/ap_state.cpp index 5b02ba6..efa94bf 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp @@ -60,6 +60,7 @@ struct APState { LocationChecks location_checks = kNORMAL_LOCATIONS; VictoryCondition victory_condition = kTHE_END; bool early_color_hallways = false; + SunwarpAccess sunwarp_access = kSUNWARP_ACCESS_NORMAL; std::map painting_mapping; @@ -128,6 +129,7 @@ struct APState { location_checks = kNORMAL_LOCATIONS; victory_condition = kTHE_END; early_color_hallways = false; + sunwarp_access = kSUNWARP_ACCESS_NORMAL; connected = false; has_connection_result = false; @@ -222,6 +224,7 @@ struct APState { slot_data["victory_condition"].get(); early_color_hallways = slot_data.contains("early_color_hallways") && slot_data["early_color_hallways"].get() == 1; + sunwarp_access = slot_data["sunwarp_access"].get(); if (painting_shuffle && slot_data.contains("painting_entrance_to_exit")) { painting_mapping.clear(); @@ -417,3 +420,5 @@ bool AP_HasAchievement(const std::string& achievement_name) { } bool AP_HasEarlyColorHallways() { return GetState().early_color_hallways; } + +SunwarpAccess AP_GetSunwarpAccess() { return GetState().sunwarp_access; } -- cgit 1.4.1 From efa0587b4399a45faecf5aa941ff75a40595a124 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Mon, 26 Feb 2024 19:15:20 -0500 Subject: Added real pilgrimage detection --- assets/areas.yaml | 4 + assets/pilgrimage.yaml | 45 ------ src/ap_state.cpp | 6 + src/ap_state.h | 2 + src/game_data.cpp | 103 ++++-------- src/game_data.h | 14 +- src/tracker_state.cpp | 417 ++++++++++++++++++++++++++++--------------------- 7 files changed, 293 insertions(+), 298 deletions(-) delete mode 100644 assets/pilgrimage.yaml (limited to 'src/ap_state.cpp') diff --git a/assets/areas.yaml b/assets/areas.yaml index a1e3423..d38ceb8 100755 --- a/assets/areas.yaml +++ b/assets/areas.yaml @@ -35,8 +35,12 @@ fold_into: Symmetry Room Outside The Agreeable: map: [1766, 700] + Compass Room: + fold_into: Outside The Agreeable Hallway Room: map: [573, 1631] + Hallway Room (1): + fold_into: Hallway Room Hallway Room (2): fold_into: Hallway Room Hallway Room (3): diff --git a/assets/pilgrimage.yaml b/assets/pilgrimage.yaml deleted file mode 100644 index 04487da..0000000 --- a/assets/pilgrimage.yaml +++ /dev/null @@ -1,45 +0,0 @@ ---- - - room: Second Room - door: Exit Door - - room: Hub Room - door: 1 Sunwarp - sunwarp: True - - room: Crossroads - door: Tower Entrance - - room: Orange Tower Fourth Floor - door: Hot Crusts Door - - room: Hot Crusts Area - door: 2 Sunwarp - sunwarp: True - - room: Orange Tower Third Floor - door: 3 Sunwarp - sunwarp: True - - room: Outside The Initiated - door: Shortcut to Hub Room - - room: Orange Tower First Floor - door: Shortcut to Hub Room - - room: Orange Tower First Floor - door: 4 Sunwarp - sunwarp: True - - room: Directional Gallery - door: Shortcut to The Undeterred - - room: Orange Tower First Floor - door: Salt Pepper Door - - room: Hub Room - door: Crossroads Entrance - - room: Orange Tower Fourth Floor - door: 5 Sunwarp - sunwarp: True - - room: Color Hunt - door: Shortcut to The Steady - - room: The Bearer - door: Entrance - - room: Art Gallery - door: Exit - - room: The Tenacious - door: Shortcut to Hub Room - - room: Outside The Agreeable - door: Tenacious Entrance - - room: Outside The Agreeable - door: 6 Sunwarp - sunwarp: True diff --git a/src/ap_state.cpp b/src/ap_state.cpp index efa94bf..ea74c93 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp @@ -60,6 +60,7 @@ struct APState { LocationChecks location_checks = kNORMAL_LOCATIONS; VictoryCondition victory_condition = kTHE_END; bool early_color_hallways = false; + bool pilgrimage_enabled = false; SunwarpAccess sunwarp_access = kSUNWARP_ACCESS_NORMAL; std::map painting_mapping; @@ -129,6 +130,7 @@ struct APState { location_checks = kNORMAL_LOCATIONS; victory_condition = kTHE_END; early_color_hallways = false; + pilgrimage_enabled = false; sunwarp_access = kSUNWARP_ACCESS_NORMAL; connected = false; @@ -224,6 +226,8 @@ struct APState { slot_data["victory_condition"].get(); early_color_hallways = slot_data.contains("early_color_hallways") && slot_data["early_color_hallways"].get() == 1; + pilgrimage_enabled = slot_data.contains("enable_pilgrimage") && + slot_data["enable_pilgrimage"].get() == 1; sunwarp_access = slot_data["sunwarp_access"].get(); if (painting_shuffle && slot_data.contains("painting_entrance_to_exit")) { @@ -421,4 +425,6 @@ bool AP_HasAchievement(const std::string& achievement_name) { bool AP_HasEarlyColorHallways() { return GetState().early_color_hallways; } +bool AP_IsPilgrimageEnabled() { return GetState().pilgrimage_enabled; } + SunwarpAccess AP_GetSunwarpAccess() { return GetState().sunwarp_access; } diff --git a/src/ap_state.h b/src/ap_state.h index 0231628..e145066 100644 --- a/src/ap_state.h +++ b/src/ap_state.h @@ -46,6 +46,8 @@ bool AP_HasAchievement(const std::string& achievement_name); bool AP_HasEarlyColorHallways(); +bool AP_IsPilgrimageEnabled(); + SunwarpAccess AP_GetSunwarpAccess(); #endif /* end of include guard: AP_STATE_H_664A4180 */ diff --git a/src/game_data.cpp b/src/game_data.cpp index dd6e924..28ca598 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp @@ -58,9 +58,6 @@ struct GameData { std::map ap_id_by_color_; - std::vector pilgrimage_; - std::vector pilgrimage_with_sunwarps_; - bool loaded_area_data_ = false; std::set malconfigured_areas_; @@ -69,8 +66,6 @@ struct GameData { YAML::LoadFile(GetAbsolutePath("assets/LL1.yaml")); YAML::Node areas_config = YAML::LoadFile(GetAbsolutePath("assets/areas.yaml")); - YAML::Node pilgrimage_config = - YAML::LoadFile(GetAbsolutePath("assets/pilgrimage.yaml")); YAML::Node ids_config = YAML::LoadFile(GetAbsolutePath("assets/ids.yaml")); auto init_color_id = [this, &ids_config](const std::string &color_name) { @@ -105,6 +100,34 @@ struct GameData { for (const auto &entrance_it : room_it.second["entrances"]) { int from_room_id = AddOrGetRoom(entrance_it.first.as()); + auto process_single_entrance = + [this, room_id, from_room_id](const YAML::Node &option) { + Exit exit_obj; + exit_obj.destination_room = room_id; + + if (option["door"]) { + std::string door_room = rooms_[room_id].name; + if (option["room"]) { + door_room = option["room"].as(); + } + exit_obj.door = AddOrGetDoor(door_room, option["door"].as()); + } + + if (option["painting"] && option["painting"].as()) { + exit_obj.type = EntranceType::kPainting; + } + + if (option["sunwarp"] && option["sunwarp"].as()) { + exit_obj.type = EntranceType::kSunwarp; + } + + if (option["warp"] && option["warp"].as()) { + exit_obj.type = EntranceType::kWarp; + } + + rooms_[from_room_id].exits.push_back(exit_obj); + }; + switch (entrance_it.second.Type()) { case YAML::NodeType::Scalar: { // This is just "true". @@ -112,50 +135,12 @@ struct GameData { break; } case YAML::NodeType::Map: { - Exit exit_obj; - exit_obj.destination_room = room_id; - - if (entrance_it.second["door"]) { - std::string door_room = rooms_[room_id].name; - if (entrance_it.second["room"]) { - door_room = entrance_it.second["room"].as(); - } - exit_obj.door = AddOrGetDoor( - door_room, entrance_it.second["door"].as()); - } - - if (entrance_it.second["painting"]) { - exit_obj.painting = entrance_it.second["painting"].as(); - } - - if (entrance_it.second["sunwarp"]) { - exit_obj.sunwarp = entrance_it.second["sunwarp"].as(); - } - - rooms_[from_room_id].exits.push_back(exit_obj); + process_single_entrance(entrance_it.second); break; } case YAML::NodeType::Sequence: { for (const auto &option : entrance_it.second) { - Exit exit_obj; - exit_obj.destination_room = room_id; - - std::string door_room = rooms_[room_id].name; - if (option["room"]) { - door_room = option["room"].as(); - } - exit_obj.door = - AddOrGetDoor(door_room, option["door"].as()); - - if (option["painting"]) { - exit_obj.painting = option["painting"].as(); - } - - if (option["sunwarp"]) { - exit_obj.sunwarp = option["sunwarp"].as(); - } - - rooms_[from_room_id].exits.push_back(exit_obj); + process_single_entrance(option); } break; @@ -586,22 +571,6 @@ struct GameData { } } - // Set up fake pilgrimage. - for (const auto &config_node : pilgrimage_config) { - int door_id = AddOrGetDoor(config_node["room"].as(), - config_node["door"].as()); - if (config_node["sunwarp"] && config_node["sunwarp"].as()) { - pilgrimage_with_sunwarps_.push_back(door_id); - } - pilgrimage_.push_back(door_id); - } - - int starting_room_id = AddOrGetRoom("Starting Room"); - Room &starting_room_obj = rooms_[starting_room_id]; - starting_room_obj.exits.push_back( - Exit{.destination_room = AddOrGetRoom("Pilgrim Antechamber"), - .pilgrimage = true}); - // Report errors. for (const std::string &area : malconfigured_areas_) { std::ostringstream errstr; @@ -679,6 +648,10 @@ const std::vector &GD_GetDoors() { return GetState().doors_; } const Door &GD_GetDoor(int door_id) { return GetState().doors_.at(door_id); } +int GD_GetDoorByName(const std::string &name) { + return GetState().door_by_id_.at(name); +} + const Panel &GD_GetPanel(int panel_id) { return GetState().panels_.at(panel_id); } @@ -694,11 +667,3 @@ const std::vector &GD_GetAchievementPanels() { int GD_GetItemIdForColor(LingoColor color) { return GetState().ap_id_by_color_.at(color); } - -const std::vector &GD_GetPilgrimageDoors(bool include_sunwarps) { - if (include_sunwarps) { - return GetState().pilgrimage_with_sunwarps_; - } else { - return GetState().pilgrimage_; - } -} diff --git a/src/game_data.h b/src/game_data.h index c230034..2c18588 100644 --- a/src/game_data.h +++ b/src/game_data.h @@ -23,6 +23,14 @@ constexpr int kLOCATION_NORMAL = 1; constexpr int kLOCATION_REDUCED = 2; constexpr int kLOCATION_INSANITY = 4; +enum class EntranceType { + kNormal, + kPainting, + kSunwarp, + kWarp, + kPilgrimage, +}; + struct Panel { int id; int room; @@ -66,9 +74,7 @@ struct Door { struct Exit { int destination_room; std::optional door; - bool painting = false; - bool sunwarp = false; - bool pilgrimage = false; + EntranceType type = EntranceType::kNormal; }; struct PaintingExit { @@ -109,10 +115,10 @@ int GD_GetRoomByName(const std::string& name); const Room& GD_GetRoom(int room_id); const std::vector& GD_GetDoors(); const Door& GD_GetDoor(int door_id); +int GD_GetDoorByName(const std::string& name); const Panel& GD_GetPanel(int panel_id); int GD_GetRoomForPainting(const std::string& painting_id); const std::vector& GD_GetAchievementPanels(); int GD_GetItemIdForColor(LingoColor color); -const std::vector& GD_GetPilgrimageDoors(bool include_sunwarps); #endif /* end of include guard: GAME_DATA_H_9C42AC51 */ diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp index 43f84b4..cc941ef 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp @@ -24,242 +24,299 @@ TrackerState& GetState() { return *instance; } -Decision IsDoorReachable_Helper(int door_id, - const std::set& reachable_rooms, - const std::set& solveable_panels) { - const Door& door_obj = GD_GetDoor(door_id); +struct StateCalculatorOptions { + std::string start = "Menu"; + bool pilgrimage = false; +}; - if (AP_GetDoorShuffleMode() == kNO_DOORS || door_obj.skip_item) { - if (!reachable_rooms.count(door_obj.room)) { - return kMaybe; - } +class StateCalculator { + public: + StateCalculator() = default; - for (int panel_id : door_obj.panels) { - if (!solveable_panels.count(panel_id)) { - return kMaybe; - } - } + explicit StateCalculator(StateCalculatorOptions options) + : options_(options) {} - return kYes; - } else if (AP_GetDoorShuffleMode() == kSIMPLE_DOORS && - !door_obj.group_name.empty()) { - return AP_HasItem(door_obj.group_ap_item_id) ? kYes : kNo; - } else { - bool has_item = AP_HasItem(door_obj.ap_item_id); + void Calculate() { + std::list panel_boundary; + std::list flood_boundary; + flood_boundary.push_back( + {.destination_room = GD_GetRoomByName(options_.start)}); + + bool reachable_changed = true; + while (reachable_changed) { + reachable_changed = false; - if (!has_item) { - for (const ProgressiveRequirement& prog_req : door_obj.progressives) { - if (AP_HasItem(prog_req.ap_item_id, prog_req.quantity)) { - has_item = true; - break; + std::list new_panel_boundary; + for (int panel_id : panel_boundary) { + if (solveable_panels_.count(panel_id)) { + continue; + } + + Decision panel_reachable = IsPanelReachable(panel_id); + if (panel_reachable == kYes) { + solveable_panels_.insert(panel_id); + reachable_changed = true; + } else if (panel_reachable == kMaybe) { + new_panel_boundary.push_back(panel_id); } } - } - return has_item ? kYes : kNo; - } -} + std::list new_boundary; + for (const Exit& room_exit : flood_boundary) { + if (reachable_rooms_.count(room_exit.destination_room)) { + continue; + } -Decision IsPanelReachable_Helper(int panel_id, - const std::set& reachable_rooms, - const std::set& solveable_panels) { - const Panel& panel_obj = GD_GetPanel(panel_id); + bool valid_transition = false; - if (!reachable_rooms.count(panel_obj.room)) { - return kMaybe; - } + Decision exit_usable = IsExitUsable(room_exit); + if (exit_usable == kYes) { + valid_transition = true; + } else if (exit_usable == kMaybe) { + new_boundary.push_back(room_exit); + } + + if (valid_transition) { + reachable_rooms_.insert(room_exit.destination_room); + reachable_changed = true; + + const Room& room_obj = GD_GetRoom(room_exit.destination_room); + for (const Exit& out_edge : room_obj.exits) { + if (out_edge.type != EntranceType::kPainting || + !AP_IsPaintingShuffle()) { + new_boundary.push_back(out_edge); + } + } + + if (AP_IsPaintingShuffle()) { + for (const PaintingExit& out_edge : room_obj.paintings) { + if (AP_GetPaintingMapping().count(out_edge.id)) { + Exit painting_exit; + painting_exit.destination_room = GD_GetRoomForPainting( + AP_GetPaintingMapping().at(out_edge.id)); + painting_exit.door = out_edge.door; + + new_boundary.push_back(painting_exit); + } + } + } - if (panel_obj.name == "THE MASTER") { - int achievements_accessible = 0; + if (AP_HasEarlyColorHallways() && room_obj.name == "Starting Room") { + new_boundary.push_back( + {.destination_room = GD_GetRoomByName("Outside The Undeterred"), + .type = EntranceType::kPainting}); + } - for (int achieve_id : GD_GetAchievementPanels()) { - if (solveable_panels.count(achieve_id)) { - achievements_accessible++; + if (AP_IsPilgrimageEnabled()) { + if (room_obj.name == "Hub Room") { + new_boundary.push_back( + {.destination_room = GD_GetRoomByName("Pilgrim Antechamber"), + .type = EntranceType::kPilgrimage}); + } + } else { + if (room_obj.name == "Starting Room") { + new_boundary.push_back( + {.destination_room = GD_GetRoomByName("Pilgrim Antechamber"), + .door = + GD_GetDoorByName("Pilgrim Antechamber - Sun Painting"), + .type = EntranceType::kPainting}); + } + } - if (achievements_accessible >= AP_GetMasteryRequirement()) { - break; + for (int panel_id : room_obj.panels) { + new_panel_boundary.push_back(panel_id); + } } } - } - return (achievements_accessible >= AP_GetMasteryRequirement()) ? kYes - : kMaybe; + flood_boundary = new_boundary; + panel_boundary = new_panel_boundary; + } } - if ((panel_obj.name == "ANOTHER TRY" || panel_obj.name == "LEVEL 2") && - AP_GetLevel2Requirement() > 1) { - int counting_panels_accessible = 0; + const std::set& GetReachableRooms() const { return reachable_rooms_; } - for (int solved_panel_id : solveable_panels) { - const Panel& solved_panel = GD_GetPanel(solved_panel_id); + const std::set& GetSolveablePanels() const { return solveable_panels_; } - if (!solved_panel.non_counting) { - counting_panels_accessible++; + private: + Decision IsDoorReachable(int door_id) { + const Door& door_obj = GD_GetDoor(door_id); + + if (!AP_IsPilgrimageEnabled() && + door_obj.item_name == "Pilgrim Room - Sun Painting") { + return AP_HasItem(door_obj.ap_item_id) ? kYes : kNo; + } else if (AP_GetDoorShuffleMode() == kNO_DOORS || door_obj.skip_item) { + if (!reachable_rooms_.count(door_obj.room)) { + return kMaybe; } - } - return (counting_panels_accessible >= AP_GetLevel2Requirement() - 1) - ? kYes - : kMaybe; - } + for (int panel_id : door_obj.panels) { + if (!solveable_panels_.count(panel_id)) { + return kMaybe; + } + } - for (int room_id : panel_obj.required_rooms) { - if (!reachable_rooms.count(room_id)) { - return kMaybe; - } - } + return kYes; + } else if (AP_GetDoorShuffleMode() == kSIMPLE_DOORS && + !door_obj.group_name.empty()) { + return AP_HasItem(door_obj.group_ap_item_id) ? kYes : kNo; + } else { + bool has_item = AP_HasItem(door_obj.ap_item_id); + + if (!has_item) { + for (const ProgressiveRequirement& prog_req : door_obj.progressives) { + if (AP_HasItem(prog_req.ap_item_id, prog_req.quantity)) { + has_item = true; + break; + } + } + } - for (int door_id : panel_obj.required_doors) { - Decision door_reachable = - IsDoorReachable_Helper(door_id, reachable_rooms, solveable_panels); - if (door_reachable == kNo) { - const Door& door_obj = GD_GetDoor(door_id); - return (door_obj.is_event || AP_GetDoorShuffleMode() == kNO_DOORS) - ? kMaybe - : kNo; - } else if (door_reachable == kMaybe) { - return kMaybe; + return has_item ? kYes : kNo; } } - for (int panel_id : panel_obj.required_panels) { - if (!solveable_panels.count(panel_id)) { + Decision IsPanelReachable(int panel_id) { + const Panel& panel_obj = GD_GetPanel(panel_id); + + if (!reachable_rooms_.count(panel_obj.room)) { return kMaybe; } - } - if (AP_IsColorShuffle()) { - for (LingoColor color : panel_obj.colors) { - if (!AP_HasItem(GD_GetItemIdForColor(color))) { - return kNo; - } - } - } + if (panel_obj.name == "THE MASTER") { + int achievements_accessible = 0; - return kYes; -} + for (int achieve_id : GD_GetAchievementPanels()) { + if (solveable_panels_.count(achieve_id)) { + achievements_accessible++; -} // namespace + if (achievements_accessible >= AP_GetMasteryRequirement()) { + break; + } + } + } -void RecalculateReachability() { - std::set reachable_rooms; - std::set solveable_panels; + return (achievements_accessible >= AP_GetMasteryRequirement()) ? kYes + : kMaybe; + } - std::list panel_boundary; - std::list flood_boundary; - flood_boundary.push_back({.destination_room = GD_GetRoomByName("Menu")}); + if ((panel_obj.name == "ANOTHER TRY" || panel_obj.name == "LEVEL 2") && + AP_GetLevel2Requirement() > 1) { + int counting_panels_accessible = 0; - if (AP_HasEarlyColorHallways()) { - flood_boundary.push_back( - {.destination_room = GD_GetRoomByName("Outside The Undeterred")}); - } + for (int solved_panel_id : solveable_panels_) { + const Panel& solved_panel = GD_GetPanel(solved_panel_id); - bool reachable_changed = true; - while (reachable_changed) { - reachable_changed = false; + if (!solved_panel.non_counting) { + counting_panels_accessible++; + } + } + + return (counting_panels_accessible >= AP_GetLevel2Requirement() - 1) + ? kYes + : kMaybe; + } - std::list new_panel_boundary; - for (int panel_id : panel_boundary) { - if (solveable_panels.count(panel_id)) { - continue; + for (int room_id : panel_obj.required_rooms) { + if (!reachable_rooms_.count(room_id)) { + return kMaybe; } + } - Decision panel_reachable = - IsPanelReachable_Helper(panel_id, reachable_rooms, solveable_panels); - if (panel_reachable == kYes) { - solveable_panels.insert(panel_id); - reachable_changed = true; - } else if (panel_reachable == kMaybe) { - new_panel_boundary.push_back(panel_id); + for (int door_id : panel_obj.required_doors) { + Decision door_reachable = IsDoorReachable(door_id); + if (door_reachable == kNo) { + const Door& door_obj = GD_GetDoor(door_id); + return (door_obj.is_event || AP_GetDoorShuffleMode() == kNO_DOORS) + ? kMaybe + : kNo; + } else if (door_reachable == kMaybe) { + return kMaybe; } } - std::list new_boundary; - for (const Exit& room_exit : flood_boundary) { - if (reachable_rooms.count(room_exit.destination_room)) { - continue; + for (int panel_id : panel_obj.required_panels) { + if (!solveable_panels_.count(panel_id)) { + return kMaybe; } + } - bool valid_transition = false; - if (room_exit.door.has_value()) { - Decision door_reachable = kMaybe; - if (room_exit.sunwarp) { - if (AP_GetSunwarpAccess() == kSUNWARP_ACCESS_NORMAL) { - door_reachable = kYes; - } else if (AP_GetSunwarpAccess() == kSUNWARP_ACCESS_DISABLED) { - door_reachable = kNo; - } else { - door_reachable = IsDoorReachable_Helper( - *room_exit.door, reachable_rooms, solveable_panels); - } - } else { - door_reachable = IsDoorReachable_Helper( - *room_exit.door, reachable_rooms, solveable_panels); - } - if (door_reachable == kYes) { - valid_transition = true; - } else if (door_reachable == kMaybe) { - new_boundary.push_back(room_exit); - } - } else if (room_exit.pilgrimage) { - Decision pilgrimage_reachable = kYes; - if (AP_GetSunwarpAccess() == kSUNWARP_ACCESS_DISABLED) { - pilgrimage_reachable = kNo; + if (AP_IsColorShuffle()) { + for (LingoColor color : panel_obj.colors) { + if (!AP_HasItem(GD_GetItemIdForColor(color))) { + return kNo; } - if (pilgrimage_reachable == kYes) { - for (int door_id : GD_GetPilgrimageDoors( - AP_GetSunwarpAccess() == kSUNWARP_ACCESS_UNLOCK || - AP_GetSunwarpAccess() == kSUNWARP_ACCESS_PROGRESSIVE)) { - pilgrimage_reachable = IsDoorReachable_Helper( - door_id, reachable_rooms, solveable_panels); - if (pilgrimage_reachable != kYes) { - break; - } - } - } - if (pilgrimage_reachable == kYes) { - valid_transition = true; - } else if (pilgrimage_reachable == kMaybe) { - new_boundary.push_back(room_exit); - } - } else { - valid_transition = true; } + } - if (valid_transition) { - reachable_rooms.insert(room_exit.destination_room); - reachable_changed = true; + return kYes; + } - const Room& room_obj = GD_GetRoom(room_exit.destination_room); - for (const Exit& out_edge : room_obj.exits) { - if (!out_edge.painting || !AP_IsPaintingShuffle()) { - new_boundary.push_back(out_edge); - } + Decision IsExitUsable(const Exit& room_exit) { + if (room_exit.type == EntranceType::kPilgrimage) { + if (options_.pilgrimage || !AP_IsPilgrimageEnabled()) { + return kNo; + } + + static const std::vector> + pilgrimage_pairs = { + {"Crossroads", "Hot Crusts Area"}, + // {"Orange Tower Third Floor", "Orange Tower Third Floor"}, + {"Outside The Initiated", "Orange Tower First Floor"}, + {"Outside The Undeterred", "Orange Tower Fourth Floor"}, + {"Color Hunt", "Outside The Agreeable"}}; + + for (const auto& [from_room, to_room] : pilgrimage_pairs) { + StateCalculator pilgrimage_calculator( + {.start = from_room, .pilgrimage = true}); + pilgrimage_calculator.Calculate(); + + if (!pilgrimage_calculator.GetReachableRooms().count( + GD_GetRoomByName(to_room))) { + return kMaybe; } + } - if (AP_IsPaintingShuffle()) { - for (const PaintingExit& out_edge : room_obj.paintings) { - if (AP_GetPaintingMapping().count(out_edge.id)) { - Exit painting_exit; - painting_exit.destination_room = GD_GetRoomForPainting( - AP_GetPaintingMapping().at(out_edge.id)); - painting_exit.door = out_edge.door; + return kYes; + } - new_boundary.push_back(painting_exit); - } - } - } + if (options_.pilgrimage) { + if (room_exit.type == EntranceType::kWarp || + room_exit.type == EntranceType::kSunwarp) { + return kNo; + } + } - for (int panel_id : room_obj.panels) { - new_panel_boundary.push_back(panel_id); - } + if (room_exit.type == EntranceType::kSunwarp) { + if (AP_GetSunwarpAccess() == kSUNWARP_ACCESS_NORMAL) { + return kYes; + } else if (AP_GetSunwarpAccess() == kSUNWARP_ACCESS_DISABLED) { + return kNo; } } - flood_boundary = new_boundary; - panel_boundary = new_panel_boundary; + if (room_exit.door.has_value()) { + return IsDoorReachable(*room_exit.door); + } + + return kYes; } + StateCalculatorOptions options_; + + std::set reachable_rooms_; + std::set solveable_panels_; +}; + +} // namespace + +void RecalculateReachability() { + StateCalculator state_calculator; + state_calculator.Calculate(); + + const std::set& reachable_rooms = state_calculator.GetReachableRooms(); + const std::set& solveable_panels = state_calculator.GetSolveablePanels(); + std::map new_reachability; for (const MapArea& map_area : GD_GetMapAreas()) { for (size_t section_id = 0; section_id < map_area.locations.size(); -- cgit 1.4.1 From 2c2d9e9f39ea780b5a04159f9c62fd5540471b86 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Wed, 28 Feb 2024 11:40:26 -0500 Subject: Fix pilgrimage detection for vanilla doors Also add new pilgrimage options. --- src/ap_state.cpp | 18 +++++++++++ src/ap_state.h | 29 ++++++++++++++---- src/game_data.cpp | 19 ++++++++++++ src/game_data.h | 9 ++++++ src/tracker_state.cpp | 83 ++++++++++++++++++++++++++++++++++++++++++--------- 5 files changed, 139 insertions(+), 19 deletions(-) (limited to 'src/ap_state.cpp') diff --git a/src/ap_state.cpp b/src/ap_state.cpp index 1937597..bdd9cb2 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp @@ -64,6 +64,8 @@ struct APState { VictoryCondition victory_condition = kTHE_END; bool early_color_hallways = false; bool pilgrimage_enabled = false; + bool pilgrimage_allows_roof_access = false; + bool pilgrimage_allows_paintings = false; SunwarpAccess sunwarp_access = kSUNWARP_ACCESS_NORMAL; std::map painting_mapping; @@ -135,6 +137,8 @@ struct APState { victory_condition = kTHE_END; early_color_hallways = false; pilgrimage_enabled = false; + pilgrimage_allows_roof_access = false; + pilgrimage_allows_paintings = false; sunwarp_access = kSUNWARP_ACCESS_NORMAL; connected = false; @@ -240,6 +244,12 @@ struct APState { slot_data["early_color_hallways"].get() == 1; pilgrimage_enabled = slot_data.contains("enable_pilgrimage") && slot_data["enable_pilgrimage"].get() == 1; + pilgrimage_allows_roof_access = + slot_data.contains("pilgrimage_allows_roof_access") && + slot_data["pilgrimage_allows_roof_access"].get() == 1; + pilgrimage_allows_paintings = + slot_data.contains("pilgrimage_allows_paintings") && + slot_data["pilgrimage_allows_paintings"].get() == 1; sunwarp_access = slot_data["sunwarp_access"].get(); if (painting_shuffle && slot_data.contains("painting_entrance_to_exit")) { @@ -454,6 +464,14 @@ bool AP_HasEarlyColorHallways() { return GetState().early_color_hallways; } bool AP_IsPilgrimageEnabled() { return GetState().pilgrimage_enabled; } +bool AP_DoesPilgrimageAllowRoofAccess() { + return GetState().pilgrimage_allows_roof_access; +} + +bool AP_DoesPilgrimageAllowPaintings() { + return GetState().pilgrimage_allows_paintings; +} + SunwarpAccess AP_GetSunwarpAccess() { return GetState().sunwarp_access; } bool AP_HasReachedGoal() { return GetState().HasReachedGoal(); } diff --git a/src/ap_state.h b/src/ap_state.h index e3c2d7f..e1f34c7 100644 --- a/src/ap_state.h +++ b/src/ap_state.h @@ -10,11 +10,26 @@ class TrackerFrame; enum DoorShuffleMode { kNO_DOORS = 0, kSIMPLE_DOORS = 1, kCOMPLEX_DOORS = 2 }; -enum VictoryCondition { kTHE_END = 0, kTHE_MASTER = 1, kLEVEL_2 = 2, kPILGRIMAGE = 3 }; - -enum LocationChecks { kNORMAL_LOCATIONS = 0, kREDUCED_LOCATIONS = 1, kPANELSANITY = 2 }; - -enum SunwarpAccess { kSUNWARP_ACCESS_NORMAL = 0, kSUNWARP_ACCESS_DISABLED = 1, kSUNWARP_ACCESS_UNLOCK = 2, kSUNWARP_ACCESS_PROGRESSIVE = 3 }; +enum VictoryCondition { + kTHE_END = 0, + kTHE_MASTER = 1, + kLEVEL_2 = 2, + kPILGRIMAGE = 3 +}; + +enum LocationChecks { + kNORMAL_LOCATIONS = 0, + kREDUCED_LOCATIONS = 1, + kPANELSANITY = 2 +}; + +enum SunwarpAccess { + kSUNWARP_ACCESS_NORMAL = 0, + kSUNWARP_ACCESS_DISABLED = 1, + kSUNWARP_ACCESS_UNLOCK = 2, + kSUNWARP_ACCESS_INDIVIDUAL = 3, + kSUNWARP_ACCESS_PROGRESSIVE = 4 +}; void AP_SetTrackerFrame(TrackerFrame* tracker_frame); @@ -48,6 +63,10 @@ bool AP_HasEarlyColorHallways(); bool AP_IsPilgrimageEnabled(); +bool AP_DoesPilgrimageAllowRoofAccess(); + +bool AP_DoesPilgrimageAllowPaintings(); + SunwarpAccess AP_GetSunwarpAccess(); bool AP_HasReachedGoal(); diff --git a/src/game_data.cpp b/src/game_data.cpp index 28ca598..75c5b7e 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp @@ -58,6 +58,8 @@ struct GameData { std::map ap_id_by_color_; + std::vector sunwarp_doors_; + bool loaded_area_data_ = false; std::set malconfigured_areas_; @@ -125,6 +127,11 @@ struct GameData { exit_obj.type = EntranceType::kWarp; } + if (rooms_[from_room_id].name == "Crossroads" && + rooms_[room_id].name == "Roof") { + exit_obj.type == EntranceType::kCrossroadsRoofAccess; + } + rooms_[from_room_id].exits.push_back(exit_obj); }; @@ -401,6 +408,14 @@ struct GameData { doors_[door_id].exclude_reduce = !door_it.second["include_reduce"].as(); } + + if (doors_[door_id].name.ends_with(" Sunwarp")) { + sunwarp_doors_.push_back(door_id); + doors_[door_id].type = DoorType::kSunwarp; + } else if (doors_[door_id].item_name == + "Pilgrim Room - Sun Painting") { + doors_[door_id].type = DoorType::kSunPainting; + } } } @@ -667,3 +682,7 @@ const std::vector &GD_GetAchievementPanels() { int GD_GetItemIdForColor(LingoColor color) { return GetState().ap_id_by_color_.at(color); } + +const std::vector &GD_GetSunwarpDoors() { + return GetState().sunwarp_doors_; +} diff --git a/src/game_data.h b/src/game_data.h index 2c18588..16d006c 100644 --- a/src/game_data.h +++ b/src/game_data.h @@ -29,6 +29,13 @@ enum class EntranceType { kSunwarp, kWarp, kPilgrimage, + kCrossroadsRoofAccess, +}; + +enum class DoorType { + kNormal, + kSunwarp, + kSunPainting, }; struct Panel { @@ -69,6 +76,7 @@ struct Door { int ap_item_id = -1; int group_ap_item_id = -1; int ap_location_id = -1; + DoorType type = DoorType::kNormal; }; struct Exit { @@ -120,5 +128,6 @@ const Panel& GD_GetPanel(int panel_id); int GD_GetRoomForPainting(const std::string& painting_id); const std::vector& GD_GetAchievementPanels(); int GD_GetItemIdForColor(LingoColor color); +const std::vector& GD_GetSunwarpDoors(); #endif /* end of include guard: GAME_DATA_H_9C42AC51 */ diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp index cc941ef..5a99254 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp @@ -24,9 +24,12 @@ TrackerState& GetState() { return *instance; } +class StateCalculator; + struct StateCalculatorOptions { std::string start = "Menu"; bool pilgrimage = false; + StateCalculator* parent = nullptr; }; class StateCalculator { @@ -136,15 +139,43 @@ class StateCalculator { const std::set& GetReachableRooms() const { return reachable_rooms_; } + const std::map& GetDoorDecisions() const { return door_decisions_; } + const std::set& GetSolveablePanels() const { return solveable_panels_; } private: - Decision IsDoorReachable(int door_id) { + Decision IsNonGroupedDoorReachable(const Door& door_obj) { + bool has_item = AP_HasItem(door_obj.ap_item_id); + + if (!has_item) { + for (const ProgressiveRequirement& prog_req : door_obj.progressives) { + if (AP_HasItem(prog_req.ap_item_id, prog_req.quantity)) { + has_item = true; + break; + } + } + } + + return has_item ? kYes : kNo; + } + + Decision IsDoorReachable_Helper(int door_id) { const Door& door_obj = GD_GetDoor(door_id); - if (!AP_IsPilgrimageEnabled() && - door_obj.item_name == "Pilgrim Room - Sun Painting") { + if (!AP_IsPilgrimageEnabled() && door_obj.type == DoorType::kSunPainting) { return AP_HasItem(door_obj.ap_item_id) ? kYes : kNo; + } else if (door_obj.type == DoorType::kSunwarp) { + switch (AP_GetSunwarpAccess()) { + case kSUNWARP_ACCESS_NORMAL: + return kYes; + case kSUNWARP_ACCESS_DISABLED: + return kNo; + case kSUNWARP_ACCESS_UNLOCK: + return AP_HasItem(door_obj.group_ap_item_id) ? kYes : kNo; + case kSUNWARP_ACCESS_INDIVIDUAL: + case kSUNWARP_ACCESS_PROGRESSIVE: + return IsNonGroupedDoorReachable(door_obj); + } } else if (AP_GetDoorShuffleMode() == kNO_DOORS || door_obj.skip_item) { if (!reachable_rooms_.count(door_obj.room)) { return kMaybe; @@ -161,19 +192,25 @@ class StateCalculator { !door_obj.group_name.empty()) { return AP_HasItem(door_obj.group_ap_item_id) ? kYes : kNo; } else { - bool has_item = AP_HasItem(door_obj.ap_item_id); + return IsNonGroupedDoorReachable(door_obj); + } + } - if (!has_item) { - for (const ProgressiveRequirement& prog_req : door_obj.progressives) { - if (AP_HasItem(prog_req.ap_item_id, prog_req.quantity)) { - has_item = true; - break; - } - } - } + Decision IsDoorReachable(int door_id) { + if (options_.parent) { + return options_.parent->IsDoorReachable(door_id); + } - return has_item ? kYes : kNo; + if (door_decisions_.count(door_id)) { + return door_decisions_.at(door_id); } + + Decision result = IsDoorReachable_Helper(door_id); + if (result != kMaybe) { + door_decisions_[door_id] = result; + } + + return result; } Decision IsPanelReachable(int panel_id) { @@ -258,6 +295,15 @@ class StateCalculator { return kNo; } + if (AP_GetSunwarpAccess() != kSUNWARP_ACCESS_NORMAL) { + for (int door_id : GD_GetSunwarpDoors()) { + Decision sub_decision = IsDoorReachable(door_id); + if (sub_decision != kYes) { + return sub_decision; + } + } + } + static const std::vector> pilgrimage_pairs = { {"Crossroads", "Hot Crusts Area"}, @@ -268,7 +314,7 @@ class StateCalculator { for (const auto& [from_room, to_room] : pilgrimage_pairs) { StateCalculator pilgrimage_calculator( - {.start = from_room, .pilgrimage = true}); + {.start = from_room, .pilgrimage = true, .parent = this}); pilgrimage_calculator.Calculate(); if (!pilgrimage_calculator.GetReachableRooms().count( @@ -285,6 +331,14 @@ class StateCalculator { room_exit.type == EntranceType::kSunwarp) { return kNo; } + if (room_exit.type == EntranceType::kCrossroadsRoofAccess && + !AP_DoesPilgrimageAllowRoofAccess()) { + return kNo; + } + if (room_exit.type == EntranceType::kPainting && + !AP_DoesPilgrimageAllowPaintings()) { + return kNo; + } } if (room_exit.type == EntranceType::kSunwarp) { @@ -305,6 +359,7 @@ class StateCalculator { StateCalculatorOptions options_; std::set reachable_rooms_; + std::map door_decisions_; std::set solveable_panels_; }; -- cgit 1.4.1 From e07e0318666e9b05b2a78b7de0979d5706cc28b7 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Sat, 2 Mar 2024 21:17:14 -0500 Subject: Added sunwarp shuffle --- src/ap_state.cpp | 23 +++++++++++++++++++++++ src/ap_state.h | 9 +++++++++ src/connection_dialog.cpp | 8 ++++---- src/game_data.cpp | 20 +++++++++++++++++++- src/game_data.h | 2 ++ src/tracker_panel.h | 2 +- src/tracker_state.cpp | 36 ++++++++++++++++++++++++++++++------ 7 files changed, 88 insertions(+), 12 deletions(-) (limited to 'src/ap_state.cpp') diff --git a/src/ap_state.cpp b/src/ap_state.cpp index bdd9cb2..1ac6575 100644 --- a/src/ap_state.cpp +++ b/src/ap_state.cpp @@ -67,8 +67,10 @@ struct APState { bool pilgrimage_allows_roof_access = false; bool pilgrimage_allows_paintings = false; SunwarpAccess sunwarp_access = kSUNWARP_ACCESS_NORMAL; + bool sunwarp_shuffle = false; std::map painting_mapping; + std::map sunwarp_mapping; void Connect(std::string server, std::string player, std::string password) { if (!initialized) { @@ -140,6 +142,8 @@ struct APState { pilgrimage_allows_roof_access = false; pilgrimage_allows_paintings = false; sunwarp_access = kSUNWARP_ACCESS_NORMAL; + sunwarp_shuffle = false; + sunwarp_mapping.clear(); connected = false; has_connection_result = false; @@ -251,6 +255,7 @@ struct APState { slot_data.contains("pilgrimage_allows_paintings") && slot_data["pilgrimage_allows_paintings"].get() == 1; sunwarp_access = slot_data["sunwarp_access"].get(); + sunwarp_shuffle = slot_data["shuffle_sunwarps"].get() == 1; if (painting_shuffle && slot_data.contains("painting_entrance_to_exit")) { painting_mapping.clear(); @@ -261,6 +266,18 @@ struct APState { } } + if (sunwarp_shuffle && slot_data.contains("sunwarp_permutation")) { + std::vector inverted_sunwarps; + for (const auto& item : slot_data["sunwarp_permutation"]) { + inverted_sunwarps.push_back(item); + } + + for (int i = 0; i < 6; i++) { + sunwarp_mapping[inverted_sunwarps[i]] = SunwarpMapping{ + .dots = i + 1, .exit_index = inverted_sunwarps[i + 6]}; + } + } + connected = true; has_connection_result = true; @@ -474,4 +491,10 @@ bool AP_DoesPilgrimageAllowPaintings() { SunwarpAccess AP_GetSunwarpAccess() { return GetState().sunwarp_access; } +bool AP_IsSunwarpShuffle() { return GetState().sunwarp_shuffle; } + +const std::map& AP_GetSunwarpMapping() { + return GetState().sunwarp_mapping; +} + bool AP_HasReachedGoal() { return GetState().HasReachedGoal(); } diff --git a/src/ap_state.h b/src/ap_state.h index e1f34c7..36694b2 100644 --- a/src/ap_state.h +++ b/src/ap_state.h @@ -31,6 +31,11 @@ enum SunwarpAccess { kSUNWARP_ACCESS_PROGRESSIVE = 4 }; +struct SunwarpMapping { + int dots; + int exit_index; +}; + void AP_SetTrackerFrame(TrackerFrame* tracker_frame); void AP_Connect(std::string server, std::string player, std::string password); @@ -69,6 +74,10 @@ bool AP_DoesPilgrimageAllowPaintings(); SunwarpAccess AP_GetSunwarpAccess(); +bool AP_IsSunwarpShuffle(); + +const std::map& AP_GetSunwarpMapping(); + bool AP_HasReachedGoal(); #endif /* end of include guard: AP_STATE_H_664A4180 */ diff --git a/src/connection_dialog.cpp b/src/connection_dialog.cpp index 9dd9984..45be8b8 100644 --- a/src/connection_dialog.cpp +++ b/src/connection_dialog.cpp @@ -4,10 +4,10 @@ ConnectionDialog::ConnectionDialog() : wxDialog(nullptr, wxID_ANY, "Connect to Archipelago") { - server_box_ = new wxTextCtrl(this, -1, GetTrackerConfig().ap_server, wxDefaultPosition, - {300, -1}); - player_box_ = new wxTextCtrl(this, -1, GetTrackerConfig().ap_player, wxDefaultPosition, - {300, -1}); + server_box_ = new wxTextCtrl(this, -1, GetTrackerConfig().ap_server, + wxDefaultPosition, {300, -1}); + player_box_ = new wxTextCtrl(this, -1, GetTrackerConfig().ap_player, + wxDefaultPosition, {300, -1}); password_box_ = new wxTextCtrl(this, -1, GetTrackerConfig().ap_password, wxDefaultPosition, {300, -1}); diff --git a/src/game_data.cpp b/src/game_data.cpp index 75c5b7e..7db6413 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp @@ -53,6 +53,7 @@ struct GameData { std::vector door_definition_order_; std::map room_by_painting_; + std::map room_by_sunwarp_; std::vector achievement_panels_; @@ -112,7 +113,8 @@ struct GameData { if (option["room"]) { door_room = option["room"].as(); } - exit_obj.door = AddOrGetDoor(door_room, option["door"].as()); + exit_obj.door = + AddOrGetDoor(door_room, option["door"].as()); } if (option["painting"] && option["painting"].as()) { @@ -443,6 +445,18 @@ struct GameData { } } + if (room_it.second["sunwarps"]) { + for (const auto &sunwarp : room_it.second["sunwarps"]) { + int index = sunwarp["dots"].as() - 1; + if (sunwarp["direction"].as() == "exit") { + index += 6; + } + + rooms_[room_id].sunwarps.push_back(index); + room_by_sunwarp_[index] = room_id; + } + } + if (room_it.second["progression"]) { for (const auto &progression_it : room_it.second["progression"]) { std::string progressive_item_name = @@ -686,3 +700,7 @@ int GD_GetItemIdForColor(LingoColor color) { const std::vector &GD_GetSunwarpDoors() { return GetState().sunwarp_doors_; } + +int GD_GetRoomForSunwarp(int index) { + return GetState().room_by_sunwarp_.at(index); +} diff --git a/src/game_data.h b/src/game_data.h index 16d006c..cd09627 100644 --- a/src/game_data.h +++ b/src/game_data.h @@ -94,6 +94,7 @@ struct Room { std::string name; std::vector exits; std::vector paintings; + std::vector sunwarps; std::vector panels; }; @@ -129,5 +130,6 @@ int GD_GetRoomForPainting(const std::string& painting_id); const std::vector& GD_GetAchievementPanels(); int GD_GetItemIdForColor(LingoColor color); const std::vector& GD_GetSunwarpDoors(); +int GD_GetRoomForSunwarp(int index); #endif /* end of include guard: GAME_DATA_H_9C42AC51 */ diff --git a/src/tracker_panel.h b/src/tracker_panel.h index cb4f082..2dc034c 100644 --- a/src/tracker_panel.h +++ b/src/tracker_panel.h @@ -25,7 +25,7 @@ class TrackerPanel : public wxPanel { int real_y2 = 0; bool active = true; }; - + void OnPaint(wxPaintEvent &event); void OnMouseMove(wxMouseEvent &event); diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp index 5a99254..0101e98 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp @@ -85,10 +85,17 @@ class StateCalculator { const Room& room_obj = GD_GetRoom(room_exit.destination_room); for (const Exit& out_edge : room_obj.exits) { - if (out_edge.type != EntranceType::kPainting || - !AP_IsPaintingShuffle()) { - new_boundary.push_back(out_edge); + if (out_edge.type == EntranceType::kPainting && + AP_IsPaintingShuffle()) { + continue; } + + if (out_edge.type == EntranceType::kSunwarp && + AP_IsSunwarpShuffle()) { + continue; + } + + new_boundary.push_back(out_edge); } if (AP_IsPaintingShuffle()) { @@ -104,6 +111,21 @@ class StateCalculator { } } + if (AP_IsSunwarpShuffle()) { + for (int index : room_obj.sunwarps) { + if (AP_GetSunwarpMapping().count(index)) { + const SunwarpMapping& sm = AP_GetSunwarpMapping().at(index); + + Exit sunwarp_exit; + sunwarp_exit.destination_room = + GD_GetRoomForSunwarp(sm.exit_index); + sunwarp_exit.door = GD_GetSunwarpDoors().at(sm.dots - 1); + + new_boundary.push_back(sunwarp_exit); + } + } + } + if (AP_HasEarlyColorHallways() && room_obj.name == "Starting Room") { new_boundary.push_back( {.destination_room = GD_GetRoomByName("Outside The Undeterred"), @@ -139,12 +161,14 @@ class StateCalculator { const std::set& GetReachableRooms() const { return reachable_rooms_; } - const std::map& GetDoorDecisions() const { return door_decisions_; } + const std::map& GetDoorDecisions() const { + return door_decisions_; + } const std::set& GetSolveablePanels() const { return solveable_panels_; } private: - Decision IsNonGroupedDoorReachable(const Door& door_obj) { + Decision IsNonGroupedDoorReachable(const Door& door_obj) { bool has_item = AP_HasItem(door_obj.ap_item_id); if (!has_item) { @@ -209,7 +233,7 @@ class StateCalculator { if (result != kMaybe) { door_decisions_[door_id] = result; } - + return result; } -- cgit 1.4.1