diff options
author | Star Rauchenberger <fefferburbia@gmail.com> | 2024-09-08 08:51:43 -0400 |
---|---|---|
committer | Star Rauchenberger <fefferburbia@gmail.com> | 2024-09-08 08:51:43 -0400 |
commit | 0e6be5bbe8c506882e8100ccaaf13a5f58079f97 (patch) | |
tree | 4fee4d578e7936cb4052a74b93064c5ea845521f | |
parent | c443acfd0b25b3e4f3446f795777b8dd18b00e2b (diff) | |
parent | 86b4b06e78c5c71588c1b55273969a1327a6710a (diff) | |
download | lingo-ap-tracker-panels.tar.gz lingo-ap-tracker-panels.tar.bz2 lingo-ap-tracker-panels.zip |
Merge branch 'main' into panels panels
-rw-r--r-- | CHANGELOG.md | 16 | ||||
-rw-r--r-- | VERSION | 2 | ||||
-rw-r--r-- | assets/subway.yaml | 2 | ||||
-rw-r--r-- | src/area_popup.cpp | 14 | ||||
-rw-r--r-- | src/game_data.cpp | 4 | ||||
-rw-r--r-- | src/game_data.h | 4 | ||||
-rw-r--r-- | src/network_set.cpp | 13 | ||||
-rw-r--r-- | src/network_set.h | 2 | ||||
-rw-r--r-- | src/subway_map.cpp | 25 | ||||
-rw-r--r-- | src/tracker_panel.cpp | 20 | ||||
-rw-r--r-- | src/tracker_state.cpp | 13 | ||||
-rw-r--r-- | src/tracker_state.h | 2 | ||||
-rw-r--r-- | src/version.h | 2 |
13 files changed, 93 insertions, 26 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index a9ce76d..a78c24c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md | |||
@@ -1,5 +1,21 @@ | |||
1 | # lingo-ap-tracker Releases | 1 | # lingo-ap-tracker Releases |
2 | 2 | ||
3 | ## v0.11.1 - 2024-07-25 | ||
4 | |||
5 | - The Pilgrim Antechamber sunwarp on the subway map now shows all sunwarp | ||
6 | connections, and is red if a pilgrimage is not possible. | ||
7 | - The save analysis panel now uses the remote location status for non-counting | ||
8 | panels. | ||
9 | - Fixed positioning of Outside The Undeterred - Number Hunt door on subway map. | ||
10 | - Fixed subway map issue when sunwarp shuffle and individual/progressive sunwarp | ||
11 | access were combined where the icons on the map would show unshuffled access. | ||
12 | - Map area indicators now correctly treat unreachable pre-checked paintings as | ||
13 | unchecked. | ||
14 | |||
15 | Download: | ||
16 | [lingo-ap-tracker-v0.11.1-win64.zip](https://files.fourisland.com/releases/lingo-ap-tracker/lingo-ap-tracker-v0.11.1-win64.zip)<br/> | ||
17 | Source: [v0.11.1](https://code.fourisland.com/lingo-ap-tracker/tag/?h=v0.11.1) | ||
18 | |||
3 | ## v0.11.0 - 2024-07-19 | 19 | ## v0.11.0 - 2024-07-19 |
4 | 20 | ||
5 | - Added a savedata analyzer. When connected to a world, the user can open up the | 21 | - Added a savedata analyzer. When connected to a world, the user can open up the |
diff --git a/VERSION b/VERSION index e88c34f..a5de145 100644 --- a/VERSION +++ b/VERSION | |||
@@ -1 +1 @@ | |||
v0.11.0 \ No newline at end of file | v0.11.1 \ No newline at end of file | ||
diff --git a/assets/subway.yaml b/assets/subway.yaml index ede704c..dcc58b2 100644 --- a/assets/subway.yaml +++ b/assets/subway.yaml | |||
@@ -701,7 +701,7 @@ | |||
701 | - pos: [719, 1039] | 701 | - pos: [719, 1039] |
702 | room: Outside The Undeterred | 702 | room: Outside The Undeterred |
703 | door: Challenge Entrance | 703 | door: Challenge Entrance |
704 | - pos: [438, 1039] | 704 | - pos: [483, 1039] |
705 | room: Outside The Undeterred | 705 | room: Outside The Undeterred |
706 | door: Number Hunt | 706 | door: Number Hunt |
707 | - pos: [563, 1071] | 707 | - pos: [563, 1071] |
diff --git a/src/area_popup.cpp b/src/area_popup.cpp index b18ba62..8d6487e 100644 --- a/src/area_popup.cpp +++ b/src/area_popup.cpp | |||
@@ -55,7 +55,7 @@ void AreaPopup::UpdateIndicators() { | |||
55 | const Location& location = map_area.locations.at(section_id); | 55 | const Location& location = map_area.locations.at(section_id); |
56 | 56 | ||
57 | if (tracker_panel->IsPanelsMode()) { | 57 | if (tracker_panel->IsPanelsMode()) { |
58 | if (!location.panel) { | 58 | if (!location.single_panel) { |
59 | continue; | 59 | continue; |
60 | } | 60 | } |
61 | } else { | 61 | } else { |
@@ -117,12 +117,12 @@ void AreaPopup::UpdateIndicators() { | |||
117 | if (IsLocationWinCondition(location)) { | 117 | if (IsLocationWinCondition(location)) { |
118 | checked = AP_HasReachedGoal(); | 118 | checked = AP_HasReachedGoal(); |
119 | } else if (tracker_panel->IsPanelsMode()) { | 119 | } else if (tracker_panel->IsPanelsMode()) { |
120 | checked = location.panel && std::any_of( | 120 | const Panel& panel = GD_GetPanel(*location.single_panel); |
121 | location.panels.begin(), location.panels.end(), | 121 | if (panel.non_counting) { |
122 | [tracker_panel](int panel_id) { | 122 | checked = AP_HasCheckedGameLocation(location.ap_location_id); |
123 | const Panel& panel = GD_GetPanel(panel_id); | 123 | } else { |
124 | return tracker_panel->GetSolvedPanels().contains(panel.nodepath); | 124 | checked = tracker_panel->GetSolvedPanels().contains(panel.nodepath); |
125 | }); | 125 | } |
126 | } else { | 126 | } else { |
127 | checked = | 127 | checked = |
128 | AP_HasCheckedGameLocation(location.ap_location_id) || | 128 | AP_HasCheckedGameLocation(location.ap_location_id) || |
diff --git a/src/game_data.cpp b/src/game_data.cpp index 0786edb..4d448d3 100644 --- a/src/game_data.cpp +++ b/src/game_data.cpp | |||
@@ -645,7 +645,7 @@ struct GameData { | |||
645 | .panels = {panel.id}, | 645 | .panels = {panel.id}, |
646 | .classification = classification, | 646 | .classification = classification, |
647 | .hunt = panel.hunt, | 647 | .hunt = panel.hunt, |
648 | .panel = true}); | 648 | .single_panel = panel.id}); |
649 | locations_by_name[location_name] = {area_id, | 649 | locations_by_name[location_name] = {area_id, |
650 | map_area.locations.size() - 1}; | 650 | map_area.locations.size() - 1}; |
651 | } | 651 | } |
@@ -698,7 +698,7 @@ struct GameData { | |||
698 | for (const Location &location : map_area.locations) { | 698 | for (const Location &location : map_area.locations) { |
699 | map_area.classification |= location.classification; | 699 | map_area.classification |= location.classification; |
700 | map_area.hunt |= location.hunt; | 700 | map_area.hunt |= location.hunt; |
701 | map_area.panel |= location.panel; | 701 | map_area.has_single_panel |= location.single_panel.has_value(); |
702 | } | 702 | } |
703 | } | 703 | } |
704 | 704 | ||
diff --git a/src/game_data.h b/src/game_data.h index 6f287cf..9b6f3b2 100644 --- a/src/game_data.h +++ b/src/game_data.h | |||
@@ -121,7 +121,7 @@ struct Location { | |||
121 | std::vector<int> panels; | 121 | std::vector<int> panels; |
122 | int classification = 0; | 122 | int classification = 0; |
123 | bool hunt = false; | 123 | bool hunt = false; |
124 | bool panel = false; | 124 | std::optional<int> single_panel; |
125 | }; | 125 | }; |
126 | 126 | ||
127 | struct MapArea { | 127 | struct MapArea { |
@@ -133,7 +133,7 @@ struct MapArea { | |||
133 | int map_y; | 133 | int map_y; |
134 | int classification = 0; | 134 | int classification = 0; |
135 | bool hunt = false; | 135 | bool hunt = false; |
136 | bool panel = false; | 136 | bool has_single_panel = false; |
137 | }; | 137 | }; |
138 | 138 | ||
139 | enum class SubwaySunwarpType { | 139 | enum class SubwaySunwarpType { |
diff --git a/src/network_set.cpp b/src/network_set.cpp index 6d2a098..2a9e12c 100644 --- a/src/network_set.cpp +++ b/src/network_set.cpp | |||
@@ -21,6 +21,19 @@ void NetworkSet::AddLink(int id1, int id2) { | |||
21 | network_by_item_[id2].insert({id1, id2}); | 21 | network_by_item_[id2].insert({id1, id2}); |
22 | } | 22 | } |
23 | 23 | ||
24 | void NetworkSet::AddLinkToNetwork(int network_id, int id1, int id2) { | ||
25 | if (id2 > id1) { | ||
26 | // Make sure id1 < id2 | ||
27 | std::swap(id1, id2); | ||
28 | } | ||
29 | |||
30 | if (!network_by_item_.count(network_id)) { | ||
31 | network_by_item_[network_id] = {}; | ||
32 | } | ||
33 | |||
34 | network_by_item_[network_id].insert({id1, id2}); | ||
35 | } | ||
36 | |||
24 | bool NetworkSet::IsItemInNetwork(int id) const { | 37 | bool NetworkSet::IsItemInNetwork(int id) const { |
25 | return network_by_item_.count(id); | 38 | return network_by_item_.count(id); |
26 | } | 39 | } |
diff --git a/src/network_set.h b/src/network_set.h index e6f0c07..cec3f39 100644 --- a/src/network_set.h +++ b/src/network_set.h | |||
@@ -13,6 +13,8 @@ class NetworkSet { | |||
13 | 13 | ||
14 | void AddLink(int id1, int id2); | 14 | void AddLink(int id1, int id2); |
15 | 15 | ||
16 | void AddLinkToNetwork(int network_id, int id1, int id2); | ||
17 | |||
16 | bool IsItemInNetwork(int id) const; | 18 | bool IsItemInNetwork(int id) const; |
17 | 19 | ||
18 | const std::set<std::pair<int, int>>& GetNetworkGraph(int id) const; | 20 | const std::set<std::pair<int, int>>& GetNetworkGraph(int id) const; |
diff --git a/src/subway_map.cpp b/src/subway_map.cpp index 9bfedf9..5b3ff5f 100644 --- a/src/subway_map.cpp +++ b/src/subway_map.cpp | |||
@@ -107,7 +107,7 @@ void SubwayMap::OnConnect() { | |||
107 | 107 | ||
108 | if (!AP_IsSunwarpShuffle() && subway_item.sunwarp && | 108 | if (!AP_IsSunwarpShuffle() && subway_item.sunwarp && |
109 | subway_item.sunwarp->type != SubwaySunwarpType::kFinal) { | 109 | subway_item.sunwarp->type != SubwaySunwarpType::kFinal) { |
110 | std::string tag = fmt::format("subway{}", subway_item.sunwarp->dots); | 110 | std::string tag = fmt::format("sunwarp{}", subway_item.sunwarp->dots); |
111 | tagged[tag].push_back(subway_item.id); | 111 | tagged[tag].push_back(subway_item.id); |
112 | } | 112 | } |
113 | 113 | ||
@@ -119,6 +119,9 @@ void SubwayMap::OnConnect() { | |||
119 | } | 119 | } |
120 | 120 | ||
121 | if (AP_IsSunwarpShuffle()) { | 121 | if (AP_IsSunwarpShuffle()) { |
122 | SubwaySunwarp final_sunwarp{.dots = 6, .type = SubwaySunwarpType::kFinal}; | ||
123 | int final_sunwarp_item = GD_GetSubwayItemForSunwarp(final_sunwarp); | ||
124 | |||
122 | for (const auto &[index, mapping] : AP_GetSunwarpMapping()) { | 125 | for (const auto &[index, mapping] : AP_GetSunwarpMapping()) { |
123 | std::string tag = fmt::format("sunwarp{}", mapping.dots); | 126 | std::string tag = fmt::format("sunwarp{}", mapping.dots); |
124 | 127 | ||
@@ -142,6 +145,11 @@ void SubwayMap::OnConnect() { | |||
142 | 145 | ||
143 | tagged[tag].push_back(GD_GetSubwayItemForSunwarp(fromWarp)); | 146 | tagged[tag].push_back(GD_GetSubwayItemForSunwarp(fromWarp)); |
144 | tagged[tag].push_back(GD_GetSubwayItemForSunwarp(toWarp)); | 147 | tagged[tag].push_back(GD_GetSubwayItemForSunwarp(toWarp)); |
148 | |||
149 | networks_.AddLinkToNetwork( | ||
150 | final_sunwarp_item, GD_GetSubwayItemForSunwarp(fromWarp), | ||
151 | mapping.dots == 6 ? final_sunwarp_item | ||
152 | : GD_GetSubwayItemForSunwarp(toWarp)); | ||
145 | } | 153 | } |
146 | } | 154 | } |
147 | 155 | ||
@@ -555,6 +563,7 @@ void SubwayMap::Redraw() { | |||
555 | ItemDrawType draw_type = ItemDrawType::kNone; | 563 | ItemDrawType draw_type = ItemDrawType::kNone; |
556 | const wxBrush *brush_color = wxGREY_BRUSH; | 564 | const wxBrush *brush_color = wxGREY_BRUSH; |
557 | std::optional<wxColour> shade_color; | 565 | std::optional<wxColour> shade_color; |
566 | std::optional<int> subway_door = GetRealSubwayDoor(subway_item); | ||
558 | 567 | ||
559 | if (AP_HasEarlyColorHallways() && | 568 | if (AP_HasEarlyColorHallways() && |
560 | subway_item.special == "starting_room_paintings") { | 569 | subway_item.special == "starting_room_paintings") { |
@@ -570,6 +579,16 @@ void SubwayMap::Redraw() { | |||
570 | brush_color = wxRED_BRUSH; | 579 | brush_color = wxRED_BRUSH; |
571 | } | 580 | } |
572 | } | 581 | } |
582 | } else if (subway_item.sunwarp && | ||
583 | subway_item.sunwarp->type == SubwaySunwarpType::kFinal && | ||
584 | AP_IsPilgrimageEnabled()) { | ||
585 | draw_type = ItemDrawType::kBox; | ||
586 | |||
587 | if (IsPilgrimageDoable()) { | ||
588 | brush_color = wxGREEN_BRUSH; | ||
589 | } else { | ||
590 | brush_color = wxRED_BRUSH; | ||
591 | } | ||
573 | } else if (!subway_item.paintings.empty()) { | 592 | } else if (!subway_item.paintings.empty()) { |
574 | if (AP_IsPaintingShuffle()) { | 593 | if (AP_IsPaintingShuffle()) { |
575 | bool has_checked_painting = false; | 594 | bool has_checked_painting = false; |
@@ -606,10 +625,10 @@ void SubwayMap::Redraw() { | |||
606 | } else if (!subway_item.tags.empty()) { | 625 | } else if (!subway_item.tags.empty()) { |
607 | draw_type = ItemDrawType::kOwl; | 626 | draw_type = ItemDrawType::kOwl; |
608 | } | 627 | } |
609 | } else if (subway_item.door) { | 628 | } else if (subway_door) { |
610 | draw_type = ItemDrawType::kBox; | 629 | draw_type = ItemDrawType::kBox; |
611 | 630 | ||
612 | if (IsDoorOpen(*subway_item.door)) { | 631 | if (IsDoorOpen(*subway_door)) { |
613 | brush_color = wxGREEN_BRUSH; | 632 | brush_color = wxGREEN_BRUSH; |
614 | } else { | 633 | } else { |
615 | brush_color = wxRED_BRUSH; | 634 | brush_color = wxRED_BRUSH; |
diff --git a/src/tracker_panel.cpp b/src/tracker_panel.cpp index 2e1497b..27e825a 100644 --- a/src/tracker_panel.cpp +++ b/src/tracker_panel.cpp | |||
@@ -180,7 +180,7 @@ void TrackerPanel::Redraw() { | |||
180 | for (AreaIndicator &area : areas_) { | 180 | for (AreaIndicator &area : areas_) { |
181 | const MapArea &map_area = GD_GetMapArea(area.area_id); | 181 | const MapArea &map_area = GD_GetMapArea(area.area_id); |
182 | if (panels_mode_) { | 182 | if (panels_mode_) { |
183 | area.active = map_area.panel; | 183 | area.active = map_area.has_single_panel; |
184 | } else if (!AP_IsLocationVisible(map_area.classification) && | 184 | } else if (!AP_IsLocationVisible(map_area.classification) && |
185 | !(map_area.hunt && GetTrackerConfig().show_hunt_panels) && | 185 | !(map_area.hunt && GetTrackerConfig().show_hunt_panels) && |
186 | !(AP_IsPaintingShuffle() && !map_area.paintings.empty())) { | 186 | !(AP_IsPaintingShuffle() && !map_area.paintings.empty())) { |
@@ -200,11 +200,14 @@ void TrackerPanel::Redraw() { | |||
200 | if (IsLocationWinCondition(section)) { | 200 | if (IsLocationWinCondition(section)) { |
201 | has_unchecked = !AP_HasReachedGoal(); | 201 | has_unchecked = !AP_HasReachedGoal(); |
202 | } else if (panels_mode_) { | 202 | } else if (panels_mode_) { |
203 | has_unchecked = section.panel && std::any_of( | 203 | if (section.single_panel) { |
204 | section.panels.begin(), section.panels.end(), [this](int panel_id) { | 204 | const Panel &panel = GD_GetPanel(*section.single_panel); |
205 | const Panel &panel = GD_GetPanel(panel_id); | 205 | if (panel.non_counting) { |
206 | return !solved_panels_.contains(panel.nodepath); | 206 | has_unchecked = !AP_HasCheckedGameLocation(section.ap_location_id); |
207 | }); | 207 | } else { |
208 | has_unchecked = !solved_panels_.contains(panel.nodepath); | ||
209 | } | ||
210 | } | ||
208 | } else if (AP_IsLocationVisible(section.classification)) { | 211 | } else if (AP_IsLocationVisible(section.classification)) { |
209 | has_unchecked = !AP_HasCheckedGameLocation(section.ap_location_id); | 212 | has_unchecked = !AP_HasCheckedGameLocation(section.ap_location_id); |
210 | } else if (section.hunt && GetTrackerConfig().show_hunt_panels) { | 213 | } else if (section.hunt && GetTrackerConfig().show_hunt_panels) { |
@@ -223,9 +226,8 @@ void TrackerPanel::Redraw() { | |||
223 | if (AP_IsPaintingShuffle() && !panels_mode_) { | 226 | if (AP_IsPaintingShuffle() && !panels_mode_) { |
224 | for (int painting_id : map_area.paintings) { | 227 | for (int painting_id : map_area.paintings) { |
225 | const PaintingExit &painting = GD_GetPaintingExit(painting_id); | 228 | const PaintingExit &painting = GD_GetPaintingExit(painting_id); |
226 | if (!AP_IsPaintingChecked(painting.internal_id)) { | 229 | bool reachable = IsPaintingReachable(painting_id); |
227 | bool reachable = IsPaintingReachable(painting_id); | 230 | if (!reachable || !AP_IsPaintingChecked(painting.internal_id)) { |
228 | |||
229 | if (reachable) { | 231 | if (reachable) { |
230 | has_reachable_unchecked = true; | 232 | has_reachable_unchecked = true; |
231 | } else { | 233 | } else { |
diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp index 18bb499..4a49fac 100644 --- a/src/tracker_state.cpp +++ b/src/tracker_state.cpp | |||
@@ -166,6 +166,7 @@ struct TrackerState { | |||
166 | std::mutex reachability_mutex; | 166 | std::mutex reachability_mutex; |
167 | RequirementCalculator requirements; | 167 | RequirementCalculator requirements; |
168 | std::map<int, std::map<std::string, bool>> door_reports; | 168 | std::map<int, std::map<std::string, bool>> door_reports; |
169 | bool pilgrimage_doable = false; | ||
169 | }; | 170 | }; |
170 | 171 | ||
171 | enum Decision { kYes, kNo, kMaybe }; | 172 | enum Decision { kYes, kNo, kMaybe }; |
@@ -375,6 +376,8 @@ class StateCalculator { | |||
375 | return door_report_; | 376 | return door_report_; |
376 | } | 377 | } |
377 | 378 | ||
379 | bool IsPilgrimageDoable() const { return pilgrimage_doable_; } | ||
380 | |||
378 | std::string GetPathToRoom(int room_id) const { | 381 | std::string GetPathToRoom(int room_id) const { |
379 | if (!paths_.count(room_id)) { | 382 | if (!paths_.count(room_id)) { |
380 | return ""; | 383 | return ""; |
@@ -601,6 +604,8 @@ class StateCalculator { | |||
601 | } | 604 | } |
602 | } | 605 | } |
603 | 606 | ||
607 | pilgrimage_doable_ = true; | ||
608 | |||
604 | return kYes; | 609 | return kYes; |
605 | } | 610 | } |
606 | 611 | ||
@@ -641,6 +646,7 @@ class StateCalculator { | |||
641 | std::set<int> solveable_panels_; | 646 | std::set<int> solveable_panels_; |
642 | std::set<int> reachable_paintings_; | 647 | std::set<int> reachable_paintings_; |
643 | std::map<int, std::map<std::string, bool>> door_report_; | 648 | std::map<int, std::map<std::string, bool>> door_report_; |
649 | bool pilgrimage_doable_ = false; | ||
644 | 650 | ||
645 | std::map<int, std::list<int>> paths_; | 651 | std::map<int, std::list<int>> paths_; |
646 | }; | 652 | }; |
@@ -692,6 +698,7 @@ void RecalculateReachability() { | |||
692 | std::swap(GetState().reachable_doors, new_reachable_doors); | 698 | std::swap(GetState().reachable_doors, new_reachable_doors); |
693 | std::swap(GetState().reachable_paintings, reachable_paintings); | 699 | std::swap(GetState().reachable_paintings, reachable_paintings); |
694 | std::swap(GetState().door_reports, door_reports); | 700 | std::swap(GetState().door_reports, door_reports); |
701 | GetState().pilgrimage_doable = state_calculator.IsPilgrimageDoable(); | ||
695 | } | 702 | } |
696 | 703 | ||
697 | bool IsLocationReachable(int location_id) { | 704 | bool IsLocationReachable(int location_id) { |
@@ -721,3 +728,9 @@ const std::map<std::string, bool>& GetDoorRequirements(int door_id) { | |||
721 | 728 | ||
722 | return GetState().door_reports[door_id]; | 729 | return GetState().door_reports[door_id]; |
723 | } | 730 | } |
731 | |||
732 | bool IsPilgrimageDoable() { | ||
733 | std::lock_guard reachability_guard(GetState().reachability_mutex); | ||
734 | |||
735 | return GetState().pilgrimage_doable; | ||
736 | } | ||
diff --git a/src/tracker_state.h b/src/tracker_state.h index c7857a0..a8f155d 100644 --- a/src/tracker_state.h +++ b/src/tracker_state.h | |||
@@ -16,4 +16,6 @@ bool IsPaintingReachable(int painting_id); | |||
16 | 16 | ||
17 | const std::map<std::string, bool>& GetDoorRequirements(int door_id); | 17 | const std::map<std::string, bool>& GetDoorRequirements(int door_id); |
18 | 18 | ||
19 | bool IsPilgrimageDoable(); | ||
20 | |||
19 | #endif /* end of include guard: TRACKER_STATE_H_8639BC90 */ | 21 | #endif /* end of include guard: TRACKER_STATE_H_8639BC90 */ |
diff --git a/src/version.h b/src/version.h index ed5e97c..24c04b4 100644 --- a/src/version.h +++ b/src/version.h | |||
@@ -36,6 +36,6 @@ struct Version { | |||
36 | } | 36 | } |
37 | }; | 37 | }; |
38 | 38 | ||
39 | constexpr const Version kTrackerVersion = Version(0, 11, 0); | 39 | constexpr const Version kTrackerVersion = Version(0, 11, 1); |
40 | 40 | ||
41 | #endif /* end of include guard: VERSION_H_C757E53C */ \ No newline at end of file | 41 | #endif /* end of include guard: VERSION_H_C757E53C */ \ No newline at end of file |