diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/ap_state.cpp | 330 | ||||
| -rw-r--r-- | src/ap_state.h | 33 | ||||
| -rw-r--r-- | src/area_popup.cpp | 54 | ||||
| -rw-r--r-- | src/area_popup.h | 25 | ||||
| -rw-r--r-- | src/connection_dialog.cpp | 40 | ||||
| -rw-r--r-- | src/connection_dialog.h | 30 | ||||
| -rw-r--r-- | src/eye_indicator.cpp | 49 | ||||
| -rw-r--r-- | src/eye_indicator.h | 30 | ||||
| -rw-r--r-- | src/game_data.cpp | 406 | ||||
| -rw-r--r-- | src/game_data.h | 135 | ||||
| -rw-r--r-- | src/main.cpp | 21 | ||||
| -rw-r--r-- | src/tracker_config.cpp | 33 | ||||
| -rw-r--r-- | src/tracker_config.h | 19 | ||||
| -rw-r--r-- | src/tracker_frame.cpp | 86 | ||||
| -rw-r--r-- | src/tracker_frame.h | 33 | ||||
| -rw-r--r-- | src/tracker_panel.cpp | 149 | ||||
| -rw-r--r-- | src/tracker_panel.h | 39 | ||||
| -rw-r--r-- | src/tracker_state.cpp | 181 | ||||
| -rw-r--r-- | src/tracker_state.h | 19 |
19 files changed, 1712 insertions, 0 deletions
| diff --git a/src/ap_state.cpp b/src/ap_state.cpp new file mode 100644 index 0000000..22a51ee --- /dev/null +++ b/src/ap_state.cpp | |||
| @@ -0,0 +1,330 @@ | |||
| 1 | #include "ap_state.h" | ||
| 2 | |||
| 3 | #define HAS_STD_FILESYSTEM | ||
| 4 | #define _WEBSOCKETPP_CPP11_STRICT_ | ||
| 5 | #pragma comment(lib, "crypt32") | ||
| 6 | |||
| 7 | #include <hkutil/string.h> | ||
| 8 | |||
| 9 | #include <apclient.hpp> | ||
| 10 | #include <apuuid.hpp> | ||
| 11 | #include <chrono> | ||
| 12 | #include <exception> | ||
| 13 | #include <list> | ||
| 14 | #include <memory> | ||
| 15 | #include <mutex> | ||
| 16 | #include <set> | ||
| 17 | #include <thread> | ||
| 18 | #include <tuple> | ||
| 19 | |||
| 20 | #include "game_data.h" | ||
| 21 | #include "tracker_frame.h" | ||
| 22 | #include "tracker_state.h" | ||
| 23 | |||
| 24 | constexpr int AP_MAJOR = 0; | ||
| 25 | constexpr int AP_MINOR = 4; | ||
| 26 | constexpr int AP_REVISION = 0; | ||
| 27 | |||
| 28 | constexpr int ITEM_HANDLING = 7; // <- all | ||
| 29 | |||
| 30 | namespace { | ||
| 31 | |||
| 32 | APClient* apclient = nullptr; | ||
| 33 | |||
| 34 | bool initialized = false; | ||
| 35 | |||
| 36 | TrackerFrame* tracker_frame; | ||
| 37 | |||
| 38 | bool client_active = false; | ||
| 39 | std::mutex client_mutex; | ||
| 40 | |||
| 41 | bool connected = false; | ||
| 42 | bool has_connection_result = false; | ||
| 43 | |||
| 44 | std::map<int64_t, int> inventory; | ||
| 45 | std::set<int64_t> checked_locations; | ||
| 46 | |||
| 47 | std::map<std::tuple<int, int>, int64_t> ap_id_by_location_id; | ||
| 48 | std::map<std::string, int64_t> ap_id_by_item_name; | ||
| 49 | std::map<LingoColor, int64_t> ap_id_by_color; | ||
| 50 | std::map<int64_t, std::string> progressive_item_by_ap_id; | ||
| 51 | |||
| 52 | DoorShuffleMode door_shuffle_mode = kNO_DOORS; | ||
| 53 | bool color_shuffle = false; | ||
| 54 | bool painting_shuffle = false; | ||
| 55 | int mastery_requirement = 21; | ||
| 56 | |||
| 57 | std::map<std::string, std::string> painting_mapping; | ||
| 58 | |||
| 59 | void RefreshTracker() { | ||
| 60 | GetTrackerState().CalculateState(); | ||
| 61 | tracker_frame->UpdateIndicators(); | ||
| 62 | } | ||
| 63 | |||
| 64 | int64_t GetItemId(const std::string& item_name) { | ||
| 65 | int64_t ap_id = apclient->get_item_id(item_name); | ||
| 66 | if (ap_id == APClient::INVALID_NAME_ID) { | ||
| 67 | std::cout << "Could not find AP item ID for " << item_name << std::endl; | ||
| 68 | } | ||
| 69 | |||
| 70 | return ap_id; | ||
| 71 | } | ||
| 72 | |||
| 73 | void DestroyClient() { | ||
| 74 | client_active = false; | ||
| 75 | apclient->reset(); | ||
| 76 | delete apclient; | ||
| 77 | apclient = nullptr; | ||
| 78 | } | ||
| 79 | |||
| 80 | } // namespace | ||
| 81 | |||
| 82 | void AP_SetTrackerFrame(TrackerFrame* arg) { tracker_frame = arg; } | ||
| 83 | |||
| 84 | void AP_Connect(std::string server, std::string player, std::string password) { | ||
| 85 | if (!initialized) { | ||
| 86 | std::thread([]() { | ||
| 87 | for (;;) { | ||
| 88 | { | ||
| 89 | std::lock_guard client_guard(client_mutex); | ||
| 90 | if (apclient) { | ||
| 91 | apclient->poll(); | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); | ||
| 96 | } | ||
| 97 | }).detach(); | ||
| 98 | |||
| 99 | initialized = true; | ||
| 100 | } | ||
| 101 | |||
| 102 | tracker_frame->SetStatusMessage("Connecting to Archipelago server...."); | ||
| 103 | |||
| 104 | { | ||
| 105 | std::lock_guard client_guard(client_mutex); | ||
| 106 | |||
| 107 | if (apclient) { | ||
| 108 | DestroyClient(); | ||
| 109 | } | ||
| 110 | |||
| 111 | apclient = new APClient(ap_get_uuid(""), "Lingo", server); | ||
| 112 | } | ||
| 113 | |||
| 114 | inventory.clear(); | ||
| 115 | checked_locations.clear(); | ||
| 116 | door_shuffle_mode = kNO_DOORS; | ||
| 117 | color_shuffle = false; | ||
| 118 | painting_shuffle = false; | ||
| 119 | painting_mapping.clear(); | ||
| 120 | mastery_requirement = 21; | ||
| 121 | |||
| 122 | connected = false; | ||
| 123 | has_connection_result = false; | ||
| 124 | |||
| 125 | apclient->set_room_info_handler([player, password]() { | ||
| 126 | inventory.clear(); | ||
| 127 | |||
| 128 | tracker_frame->SetStatusMessage( | ||
| 129 | "Connected to Archipelago server. Authenticating..."); | ||
| 130 | |||
| 131 | apclient->ConnectSlot(player, password, ITEM_HANDLING, {"Tracker"}, | ||
| 132 | {AP_MAJOR, AP_MINOR, AP_REVISION}); | ||
| 133 | }); | ||
| 134 | |||
| 135 | apclient->set_location_checked_handler( | ||
| 136 | [](const std::list<int64_t>& locations) { | ||
| 137 | for (const int64_t location_id : locations) { | ||
| 138 | checked_locations.insert(location_id); | ||
| 139 | std::cout << "Location: " << location_id << std::endl; | ||
| 140 | } | ||
| 141 | |||
| 142 | RefreshTracker(); | ||
| 143 | }); | ||
| 144 | |||
| 145 | apclient->set_slot_disconnected_handler([]() { | ||
| 146 | tracker_frame->SetStatusMessage( | ||
| 147 | "Disconnected from Archipelago. Attempting to reconnect..."); | ||
| 148 | }); | ||
| 149 | |||
| 150 | apclient->set_socket_disconnected_handler([]() { | ||
| 151 | tracker_frame->SetStatusMessage( | ||
| 152 | "Disconnected from Archipelago. Attempting to reconnect..."); | ||
| 153 | }); | ||
| 154 | |||
| 155 | apclient->set_items_received_handler( | ||
| 156 | [](const std::list<APClient::NetworkItem>& items) { | ||
| 157 | for (const APClient::NetworkItem& item : items) { | ||
| 158 | inventory[item.item]++; | ||
| 159 | std::cout << "Item: " << item.item << std::endl; | ||
| 160 | } | ||
| 161 | |||
| 162 | RefreshTracker(); | ||
| 163 | }); | ||
| 164 | |||
| 165 | apclient->set_slot_connected_handler([](const nlohmann::json& slot_data) { | ||
| 166 | tracker_frame->SetStatusMessage("Connected to Archipelago!"); | ||
| 167 | |||
| 168 | door_shuffle_mode = slot_data["shuffle_doors"].get<DoorShuffleMode>(); | ||
| 169 | color_shuffle = slot_data["shuffle_colors"].get<bool>(); | ||
| 170 | painting_shuffle = slot_data["shuffle_paintings"].get<bool>(); | ||
| 171 | mastery_requirement = slot_data["mastery_achievements"].get<int>(); | ||
| 172 | |||
| 173 | if (painting_shuffle && slot_data.contains("painting_entrance_to_exit")) { | ||
| 174 | painting_mapping.clear(); | ||
| 175 | |||
| 176 | for (const auto& mapping_it : | ||
| 177 | slot_data["painting_entrance_to_exit"].items()) { | ||
| 178 | painting_mapping[mapping_it.key()] = mapping_it.value(); | ||
| 179 | } | ||
| 180 | } | ||
| 181 | |||
| 182 | connected = true; | ||
| 183 | has_connection_result = true; | ||
| 184 | |||
| 185 | RefreshTracker(); | ||
| 186 | }); | ||
| 187 | |||
| 188 | apclient->set_slot_refused_handler([](const std::list<std::string>& errors) { | ||
| 189 | connected = false; | ||
| 190 | has_connection_result = true; | ||
| 191 | |||
| 192 | tracker_frame->SetStatusMessage("Disconnected from Archipelago."); | ||
| 193 | |||
| 194 | std::vector<std::string> error_messages; | ||
| 195 | error_messages.push_back("Could not connect to Archipelago."); | ||
| 196 | |||
| 197 | for (const std::string& error : errors) { | ||
| 198 | if (error == "InvalidSlot") { | ||
| 199 | error_messages.push_back("Invalid player name."); | ||
| 200 | } else if (error == "InvalidGame") { | ||
| 201 | error_messages.push_back("The specified player is not playing Lingo."); | ||
| 202 | } else if (error == "IncompatibleVersion") { | ||
| 203 | error_messages.push_back( | ||
| 204 | "The Archipelago server is not the correct version for this " | ||
| 205 | "client."); | ||
| 206 | } else if (error == "InvalidPassword") { | ||
| 207 | error_messages.push_back("Incorrect password."); | ||
| 208 | } else if (error == "InvalidItemsHandling") { | ||
| 209 | error_messages.push_back( | ||
| 210 | "Invalid item handling flag. This is a bug with the tracker. " | ||
| 211 | "Please report it to the lingo-ap-tracker GitHub."); | ||
| 212 | } else { | ||
| 213 | error_messages.push_back("Unknown error."); | ||
| 214 | } | ||
| 215 | } | ||
| 216 | |||
| 217 | std::string full_message = hatkirby::implode(error_messages, " "); | ||
| 218 | |||
| 219 | wxMessageBox(full_message, "Connection failed", wxOK | wxICON_ERROR); | ||
| 220 | }); | ||
| 221 | |||
| 222 | client_active = true; | ||
| 223 | |||
| 224 | int timeout = 5000; // 5 seconds | ||
| 225 | int interval = 100; | ||
| 226 | int remaining_loops = timeout / interval; | ||
| 227 | while (!has_connection_result) { | ||
| 228 | if (interval == 0) { | ||
| 229 | connected = false; | ||
| 230 | has_connection_result = true; | ||
| 231 | |||
| 232 | DestroyClient(); | ||
| 233 | |||
| 234 | tracker_frame->SetStatusMessage("Disconnected from Archipelago."); | ||
| 235 | |||
| 236 | wxMessageBox("Timeout while connecting to Archipelago server.", | ||
| 237 | "Connection failed", wxOK | wxICON_ERROR); | ||
| 238 | } | ||
| 239 | |||
| 240 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); | ||
| 241 | |||
| 242 | interval--; | ||
| 243 | } | ||
| 244 | |||
| 245 | if (connected) { | ||
| 246 | for (const MapArea& map_area : GetGameData().GetMapAreas()) { | ||
| 247 | for (int section_id = 0; section_id < map_area.locations.size(); | ||
| 248 | section_id++) { | ||
| 249 | const Location& location = map_area.locations.at(section_id); | ||
| 250 | |||
| 251 | int64_t ap_id = apclient->get_location_id(location.ap_location_name); | ||
| 252 | if (ap_id == APClient::INVALID_NAME_ID) { | ||
| 253 | std::cout << "Could not find AP location ID for " | ||
| 254 | << location.ap_location_name << std::endl; | ||
| 255 | } else { | ||
| 256 | ap_id_by_location_id[{map_area.id, section_id}] = ap_id; | ||
| 257 | } | ||
| 258 | } | ||
| 259 | } | ||
| 260 | |||
| 261 | for (const Door& door : GetGameData().GetDoors()) { | ||
| 262 | if (!door.skip_item) { | ||
| 263 | ap_id_by_item_name[door.item_name] = GetItemId(door.item_name); | ||
| 264 | |||
| 265 | if (!door.group_name.empty() && | ||
| 266 | !ap_id_by_item_name.count(door.group_name)) { | ||
| 267 | ap_id_by_item_name[door.group_name] = GetItemId(door.group_name); | ||
| 268 | } | ||
| 269 | |||
| 270 | for (const ProgressiveRequirement& prog_req : door.progressives) { | ||
| 271 | ap_id_by_item_name[prog_req.item_name] = | ||
| 272 | GetItemId(prog_req.item_name); | ||
| 273 | } | ||
| 274 | } | ||
| 275 | } | ||
| 276 | |||
| 277 | ap_id_by_color[LingoColor::kBlack] = GetItemId("Black"); | ||
| 278 | ap_id_by_color[LingoColor::kRed] = GetItemId("Red"); | ||
| 279 | ap_id_by_color[LingoColor::kBlue] = GetItemId("Blue"); | ||
| 280 | ap_id_by_color[LingoColor::kYellow] = GetItemId("Yellow"); | ||
| 281 | ap_id_by_color[LingoColor::kPurple] = GetItemId("Purple"); | ||
| 282 | ap_id_by_color[LingoColor::kOrange] = GetItemId("Orange"); | ||
| 283 | ap_id_by_color[LingoColor::kGreen] = GetItemId("Green"); | ||
| 284 | ap_id_by_color[LingoColor::kBrown] = GetItemId("Brown"); | ||
| 285 | ap_id_by_color[LingoColor::kGray] = GetItemId("Gray"); | ||
| 286 | |||
| 287 | RefreshTracker(); | ||
| 288 | } else { | ||
| 289 | client_active = false; | ||
| 290 | } | ||
| 291 | } | ||
| 292 | |||
| 293 | bool AP_HasCheckedGameLocation(int area_id, int section_id) { | ||
| 294 | std::tuple<int, int> location_key = {area_id, section_id}; | ||
| 295 | |||
| 296 | if (ap_id_by_location_id.count(location_key)) { | ||
| 297 | return checked_locations.count(ap_id_by_location_id.at(location_key)); | ||
| 298 | } else { | ||
| 299 | return false; | ||
| 300 | } | ||
| 301 | } | ||
| 302 | |||
| 303 | bool AP_HasColorItem(LingoColor color) { | ||
| 304 | if (ap_id_by_color.count(color)) { | ||
| 305 | return inventory.count(ap_id_by_color.at(color)); | ||
| 306 | } else { | ||
| 307 | return false; | ||
| 308 | } | ||
| 309 | } | ||
| 310 | |||
| 311 | bool AP_HasItem(const std::string& item, int quantity) { | ||
| 312 | if (ap_id_by_item_name.count(item)) { | ||
| 313 | int64_t ap_id = ap_id_by_item_name.at(item); | ||
| 314 | return inventory.count(ap_id) && inventory.at(ap_id) >= quantity; | ||
| 315 | } else { | ||
| 316 | return false; | ||
| 317 | } | ||
| 318 | } | ||
| 319 | |||
| 320 | DoorShuffleMode AP_GetDoorShuffleMode() { return door_shuffle_mode; } | ||
| 321 | |||
| 322 | bool AP_IsColorShuffle() { return color_shuffle; } | ||
| 323 | |||
| 324 | bool AP_IsPaintingShuffle() { return painting_shuffle; } | ||
| 325 | |||
| 326 | const std::map<std::string, std::string> AP_GetPaintingMapping() { | ||
| 327 | return painting_mapping; | ||
| 328 | } | ||
| 329 | |||
| 330 | int AP_GetMasteryRequirement() { return mastery_requirement; } | ||
| diff --git a/src/ap_state.h b/src/ap_state.h new file mode 100644 index 0000000..d880c71 --- /dev/null +++ b/src/ap_state.h | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | #ifndef AP_STATE_H_664A4180 | ||
| 2 | #define AP_STATE_H_664A4180 | ||
| 3 | |||
| 4 | #include <map> | ||
| 5 | #include <string> | ||
| 6 | |||
| 7 | #include "game_data.h" | ||
| 8 | |||
| 9 | class TrackerFrame; | ||
| 10 | |||
| 11 | enum DoorShuffleMode { kNO_DOORS = 0, kSIMPLE_DOORS = 1, kCOMPLEX_DOORS = 2 }; | ||
| 12 | |||
| 13 | void AP_SetTrackerFrame(TrackerFrame* tracker_frame); | ||
| 14 | |||
| 15 | void AP_Connect(std::string server, std::string player, std::string password); | ||
| 16 | |||
| 17 | bool AP_HasCheckedGameLocation(int area_id, int section_id); | ||
| 18 | |||
| 19 | bool AP_HasColorItem(LingoColor color); | ||
| 20 | |||
| 21 | bool AP_HasItem(const std::string& item, int quantity = 1); | ||
| 22 | |||
| 23 | DoorShuffleMode AP_GetDoorShuffleMode(); | ||
| 24 | |||
| 25 | bool AP_IsColorShuffle(); | ||
| 26 | |||
| 27 | bool AP_IsPaintingShuffle(); | ||
| 28 | |||
| 29 | const std::map<std::string, std::string> AP_GetPaintingMapping(); | ||
| 30 | |||
| 31 | int AP_GetMasteryRequirement(); | ||
| 32 | |||
| 33 | #endif /* end of include guard: AP_STATE_H_664A4180 */ | ||
| diff --git a/src/area_popup.cpp b/src/area_popup.cpp new file mode 100644 index 0000000..a8ff612 --- /dev/null +++ b/src/area_popup.cpp | |||
| @@ -0,0 +1,54 @@ | |||
| 1 | #include "area_popup.h" | ||
| 2 | |||
| 3 | #include "ap_state.h" | ||
| 4 | #include "game_data.h" | ||
| 5 | #include "tracker_state.h" | ||
| 6 | |||
| 7 | AreaPopup::AreaPopup(wxWindow* parent, int area_id) | ||
| 8 | : wxPanel(parent, wxID_ANY), area_id_(area_id) { | ||
| 9 | const MapArea& map_area = GetGameData().GetMapArea(area_id); | ||
| 10 | |||
| 11 | wxFlexGridSizer* section_sizer = new wxFlexGridSizer(2, 10, 10); | ||
| 12 | |||
| 13 | for (const Location& location : map_area.locations) { | ||
| 14 | EyeIndicator* eye_indicator = new EyeIndicator(this); | ||
| 15 | section_sizer->Add(eye_indicator, wxSizerFlags().Expand()); | ||
| 16 | eye_indicators_.push_back(eye_indicator); | ||
| 17 | |||
| 18 | wxStaticText* section_label = new wxStaticText(this, -1, location.name); | ||
| 19 | section_label->SetForegroundColour(*wxWHITE); | ||
| 20 | section_sizer->Add( | ||
| 21 | section_label, | ||
| 22 | wxSizerFlags().Align(wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL)); | ||
| 23 | section_labels_.push_back(section_label); | ||
| 24 | } | ||
| 25 | |||
| 26 | wxBoxSizer* top_sizer = new wxBoxSizer(wxVERTICAL); | ||
| 27 | |||
| 28 | wxStaticText* top_label = new wxStaticText(this, -1, map_area.name); | ||
| 29 | top_label->SetForegroundColour(*wxWHITE); | ||
| 30 | top_label->SetFont(top_label->GetFont().Bold()); | ||
| 31 | top_sizer->Add(top_label, | ||
| 32 | wxSizerFlags().Center().DoubleBorder(wxUP | wxLEFT | wxRIGHT)); | ||
| 33 | |||
| 34 | top_sizer->Add(section_sizer, wxSizerFlags().DoubleBorder(wxALL).Expand()); | ||
| 35 | |||
| 36 | SetSizerAndFit(top_sizer); | ||
| 37 | |||
| 38 | SetBackgroundColour(*wxBLACK); | ||
| 39 | Hide(); | ||
| 40 | } | ||
| 41 | |||
| 42 | void AreaPopup::UpdateIndicators() { | ||
| 43 | const MapArea& map_area = GetGameData().GetMapArea(area_id_); | ||
| 44 | for (int section_id = 0; section_id < map_area.locations.size(); | ||
| 45 | section_id++) { | ||
| 46 | bool checked = AP_HasCheckedGameLocation(area_id_, section_id); | ||
| 47 | bool reachable = | ||
| 48 | GetTrackerState().IsLocationReachable(area_id_, section_id); | ||
| 49 | const wxColour* text_color = reachable ? wxWHITE : wxRED; | ||
| 50 | |||
| 51 | section_labels_[section_id]->SetForegroundColour(*text_color); | ||
| 52 | eye_indicators_[section_id]->SetChecked(checked); | ||
| 53 | } | ||
| 54 | } | ||
| diff --git a/src/area_popup.h b/src/area_popup.h new file mode 100644 index 0000000..b602b63 --- /dev/null +++ b/src/area_popup.h | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | #ifndef AREA_POPUP_H_03FAC988 | ||
| 2 | #define AREA_POPUP_H_03FAC988 | ||
| 3 | |||
| 4 | #include <wx/wxprec.h> | ||
| 5 | |||
| 6 | #ifndef WX_PRECOMP | ||
| 7 | #include <wx/wx.h> | ||
| 8 | #endif | ||
| 9 | |||
| 10 | #include "eye_indicator.h" | ||
| 11 | |||
| 12 | class AreaPopup : public wxPanel { | ||
| 13 | public: | ||
| 14 | AreaPopup(wxWindow* parent, int area_id); | ||
| 15 | |||
| 16 | void UpdateIndicators(); | ||
| 17 | |||
| 18 | private: | ||
| 19 | int area_id_; | ||
| 20 | |||
| 21 | std::vector<wxStaticText*> section_labels_; | ||
| 22 | std::vector<EyeIndicator*> eye_indicators_; | ||
| 23 | }; | ||
| 24 | |||
| 25 | #endif /* end of include guard: AREA_POPUP_H_03FAC988 */ | ||
| diff --git a/src/connection_dialog.cpp b/src/connection_dialog.cpp new file mode 100644 index 0000000..9dd9984 --- /dev/null +++ b/src/connection_dialog.cpp | |||
| @@ -0,0 +1,40 @@ | |||
| 1 | #include "connection_dialog.h" | ||
| 2 | |||
| 3 | #include "tracker_config.h" | ||
| 4 | |||
| 5 | ConnectionDialog::ConnectionDialog() | ||
| 6 | : wxDialog(nullptr, wxID_ANY, "Connect to Archipelago") { | ||
| 7 | server_box_ = new wxTextCtrl(this, -1, GetTrackerConfig().ap_server, wxDefaultPosition, | ||
| 8 | {300, -1}); | ||
| 9 | player_box_ = new wxTextCtrl(this, -1, GetTrackerConfig().ap_player, wxDefaultPosition, | ||
| 10 | {300, -1}); | ||
| 11 | password_box_ = new wxTextCtrl(this, -1, GetTrackerConfig().ap_password, | ||
| 12 | wxDefaultPosition, {300, -1}); | ||
| 13 | |||
| 14 | wxFlexGridSizer* form_sizer = new wxFlexGridSizer(2, 10, 10); | ||
| 15 | |||
| 16 | form_sizer->Add( | ||
| 17 | new wxStaticText(this, -1, "Server:"), | ||
| 18 | wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); | ||
| 19 | form_sizer->Add(server_box_, wxSizerFlags().Expand()); | ||
| 20 | form_sizer->Add( | ||
| 21 | new wxStaticText(this, -1, "Player:"), | ||
| 22 | wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); | ||
| 23 | form_sizer->Add(player_box_, wxSizerFlags().Expand()); | ||
| 24 | form_sizer->Add( | ||
| 25 | new wxStaticText(this, -1, "Password:"), | ||
| 26 | wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); | ||
| 27 | form_sizer->Add(password_box_, wxSizerFlags().Expand()); | ||
| 28 | |||
| 29 | wxBoxSizer* top_sizer = new wxBoxSizer(wxVERTICAL); | ||
| 30 | top_sizer->Add(new wxStaticText( | ||
| 31 | this, -1, "Enter the details to connect to Archipelago."), | ||
| 32 | wxSizerFlags().Align(wxALIGN_LEFT).DoubleBorder()); | ||
| 33 | top_sizer->Add(form_sizer, wxSizerFlags().DoubleBorder().Expand()); | ||
| 34 | top_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), wxSizerFlags().Center()); | ||
| 35 | |||
| 36 | SetSizerAndFit(top_sizer); | ||
| 37 | |||
| 38 | Center(); | ||
| 39 | server_box_->SetFocus(); | ||
| 40 | } | ||
| diff --git a/src/connection_dialog.h b/src/connection_dialog.h new file mode 100644 index 0000000..5d2703d --- /dev/null +++ b/src/connection_dialog.h | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | #ifndef CONNECTION_DIALOG_H_E526D0E7 | ||
| 2 | #define CONNECTION_DIALOG_H_E526D0E7 | ||
| 3 | |||
| 4 | #include <wx/wxprec.h> | ||
| 5 | |||
| 6 | #ifndef WX_PRECOMP | ||
| 7 | #include <wx/wx.h> | ||
| 8 | #endif | ||
| 9 | |||
| 10 | #include <string> | ||
| 11 | |||
| 12 | class ConnectionDialog : public wxDialog { | ||
| 13 | public: | ||
| 14 | ConnectionDialog(); | ||
| 15 | |||
| 16 | std::string GetServerValue() { return server_box_->GetValue().ToStdString(); } | ||
| 17 | |||
| 18 | std::string GetPlayerValue() { return player_box_->GetValue().ToStdString(); } | ||
| 19 | |||
| 20 | std::string GetPasswordValue() { | ||
| 21 | return password_box_->GetValue().ToStdString(); | ||
| 22 | } | ||
| 23 | |||
| 24 | private: | ||
| 25 | wxTextCtrl* server_box_; | ||
| 26 | wxTextCtrl* player_box_; | ||
| 27 | wxTextCtrl* password_box_; | ||
| 28 | }; | ||
| 29 | |||
| 30 | #endif /* end of include guard: CONNECTION_DIALOG_H_E526D0E7 */ | ||
| diff --git a/src/eye_indicator.cpp b/src/eye_indicator.cpp new file mode 100644 index 0000000..03e234e --- /dev/null +++ b/src/eye_indicator.cpp | |||
| @@ -0,0 +1,49 @@ | |||
| 1 | #include "eye_indicator.h" | ||
| 2 | |||
| 3 | EyeIndicator::EyeIndicator(wxWindow* parent) : wxWindow(parent, wxID_ANY) { | ||
| 4 | SetMinSize({32, 32}); | ||
| 5 | |||
| 6 | Redraw(); | ||
| 7 | |||
| 8 | Bind(wxEVT_PAINT, &EyeIndicator::OnPaint, this); | ||
| 9 | } | ||
| 10 | |||
| 11 | void EyeIndicator::SetChecked(bool checked) { | ||
| 12 | if (intended_checked_ != checked) { | ||
| 13 | intended_checked_ = checked; | ||
| 14 | |||
| 15 | Redraw(); | ||
| 16 | } | ||
| 17 | } | ||
| 18 | |||
| 19 | const wxImage& EyeIndicator::GetUncheckedImage() { | ||
| 20 | static wxImage* unchecked_image = | ||
| 21 | new wxImage("assets/unchecked.png", wxBITMAP_TYPE_PNG); | ||
| 22 | return *unchecked_image; | ||
| 23 | } | ||
| 24 | |||
| 25 | const wxImage& EyeIndicator::GetCheckedImage() { | ||
| 26 | static wxImage* checked_image = | ||
| 27 | new wxImage("assets/checked.png", wxBITMAP_TYPE_PNG); | ||
| 28 | return *checked_image; | ||
| 29 | } | ||
| 30 | |||
| 31 | void EyeIndicator::OnPaint(wxPaintEvent& event) { | ||
| 32 | if (GetSize() != rendered_.GetSize() || | ||
| 33 | intended_checked_ != rendered_checked_) { | ||
| 34 | Redraw(); | ||
| 35 | } | ||
| 36 | |||
| 37 | wxPaintDC dc(this); | ||
| 38 | dc.DrawBitmap(rendered_, 0, 0); | ||
| 39 | |||
| 40 | event.Skip(); | ||
| 41 | } | ||
| 42 | |||
| 43 | void EyeIndicator::Redraw() { | ||
| 44 | rendered_ = | ||
| 45 | wxBitmap((intended_checked_ ? GetCheckedImage() : GetUncheckedImage()) | ||
| 46 | .Scale(GetSize().GetWidth(), GetSize().GetHeight(), | ||
| 47 | wxIMAGE_QUALITY_NORMAL)); | ||
| 48 | rendered_checked_ = intended_checked_; | ||
| 49 | } | ||
| diff --git a/src/eye_indicator.h b/src/eye_indicator.h new file mode 100644 index 0000000..e8fd890 --- /dev/null +++ b/src/eye_indicator.h | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | #ifndef EYE_INDICATOR_H_778150F2 | ||
| 2 | #define EYE_INDICATOR_H_778150F2 | ||
| 3 | |||
| 4 | #include <wx/wxprec.h> | ||
| 5 | |||
| 6 | #ifndef WX_PRECOMP | ||
| 7 | #include <wx/wx.h> | ||
| 8 | #endif | ||
| 9 | |||
| 10 | class EyeIndicator : public wxWindow { | ||
| 11 | public: | ||
| 12 | EyeIndicator(wxWindow* parent); | ||
| 13 | |||
| 14 | void SetChecked(bool checked); | ||
| 15 | |||
| 16 | private: | ||
| 17 | static const wxImage& GetUncheckedImage(); | ||
| 18 | static const wxImage& GetCheckedImage(); | ||
| 19 | |||
| 20 | void OnPaint(wxPaintEvent& event); | ||
| 21 | |||
| 22 | void Redraw(); | ||
| 23 | |||
| 24 | bool intended_checked_ = false; | ||
| 25 | |||
| 26 | wxBitmap rendered_; | ||
| 27 | bool rendered_checked_ = false; | ||
| 28 | }; | ||
| 29 | |||
| 30 | #endif /* end of include guard: EYE_INDICATOR_H_778150F2 */ | ||
| diff --git a/src/game_data.cpp b/src/game_data.cpp new file mode 100644 index 0000000..e15847e --- /dev/null +++ b/src/game_data.cpp | |||
| @@ -0,0 +1,406 @@ | |||
| 1 | #include "game_data.h" | ||
| 2 | |||
| 3 | #include <hkutil/string.h> | ||
| 4 | #include <yaml-cpp/yaml.h> | ||
| 5 | |||
| 6 | #include <iostream> | ||
| 7 | |||
| 8 | LingoColor GetColorForString(const std::string &str) { | ||
| 9 | if (str == "black") { | ||
| 10 | return LingoColor::kBlack; | ||
| 11 | } else if (str == "red") { | ||
| 12 | return LingoColor::kRed; | ||
| 13 | } else if (str == "blue") { | ||
| 14 | return LingoColor::kBlue; | ||
| 15 | } else if (str == "yellow") { | ||
| 16 | return LingoColor::kYellow; | ||
| 17 | } else if (str == "orange") { | ||
| 18 | return LingoColor::kOrange; | ||
| 19 | } else if (str == "green") { | ||
| 20 | return LingoColor::kGreen; | ||
| 21 | } else if (str == "gray") { | ||
| 22 | return LingoColor::kGray; | ||
| 23 | } else if (str == "brown") { | ||
| 24 | return LingoColor::kBrown; | ||
| 25 | } else if (str == "purple") { | ||
| 26 | return LingoColor::kPurple; | ||
| 27 | } else { | ||
| 28 | std::cout << "Invalid color: " << str << std::endl; | ||
| 29 | return LingoColor::kNone; | ||
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | GameData::GameData() { | ||
| 34 | YAML::Node lingo_config = YAML::LoadFile("assets/LL1.yaml"); | ||
| 35 | YAML::Node areas_config = YAML::LoadFile("assets/areas.yaml"); | ||
| 36 | |||
| 37 | rooms_.reserve(lingo_config.size() * 2); | ||
| 38 | |||
| 39 | for (const auto &room_it : lingo_config) { | ||
| 40 | int room_id = AddOrGetRoom(room_it.first.as<std::string>()); | ||
| 41 | Room &room_obj = rooms_[room_id]; | ||
| 42 | |||
| 43 | for (const auto &entrance_it : room_it.second["entrances"]) { | ||
| 44 | int from_room_id = AddOrGetRoom(entrance_it.first.as<std::string>()); | ||
| 45 | Room &from_room_obj = rooms_[from_room_id]; | ||
| 46 | |||
| 47 | switch (entrance_it.second.Type()) { | ||
| 48 | case YAML::NodeType::Scalar: { | ||
| 49 | // This is just "true". | ||
| 50 | from_room_obj.exits.push_back({.destination_room = room_id}); | ||
| 51 | break; | ||
| 52 | } | ||
| 53 | case YAML::NodeType::Map: { | ||
| 54 | Exit exit_obj; | ||
| 55 | exit_obj.destination_room = room_id; | ||
| 56 | |||
| 57 | if (entrance_it.second["door"]) { | ||
| 58 | std::string door_room = room_obj.name; | ||
| 59 | if (entrance_it.second["room"]) { | ||
| 60 | door_room = entrance_it.second["room"].as<std::string>(); | ||
| 61 | } | ||
| 62 | exit_obj.door = AddOrGetDoor( | ||
| 63 | door_room, entrance_it.second["door"].as<std::string>()); | ||
| 64 | } | ||
| 65 | |||
| 66 | if (entrance_it.second["painting"]) { | ||
| 67 | exit_obj.painting = entrance_it.second["painting"].as<bool>(); | ||
| 68 | } | ||
| 69 | |||
| 70 | from_room_obj.exits.push_back(exit_obj); | ||
| 71 | break; | ||
| 72 | } | ||
| 73 | case YAML::NodeType::Sequence: { | ||
| 74 | for (const auto &option : entrance_it.second) { | ||
| 75 | Exit exit_obj; | ||
| 76 | exit_obj.destination_room = room_id; | ||
| 77 | |||
| 78 | std::string door_room = room_obj.name; | ||
| 79 | if (option["room"]) { | ||
| 80 | door_room = option["room"].as<std::string>(); | ||
| 81 | } | ||
| 82 | exit_obj.door = | ||
| 83 | AddOrGetDoor(door_room, option["door"].as<std::string>()); | ||
| 84 | |||
| 85 | if (option["painting"]) { | ||
| 86 | exit_obj.painting = option["painting"].as<bool>(); | ||
| 87 | } | ||
| 88 | |||
| 89 | from_room_obj.exits.push_back(exit_obj); | ||
| 90 | } | ||
| 91 | |||
| 92 | break; | ||
| 93 | } | ||
| 94 | default: { | ||
| 95 | // This shouldn't happen. | ||
| 96 | std::cout << "Error reading game data: " << entrance_it << std::endl; | ||
| 97 | break; | ||
| 98 | } | ||
| 99 | } | ||
| 100 | } | ||
| 101 | |||
| 102 | if (room_it.second["panels"]) { | ||
| 103 | for (const auto &panel_it : room_it.second["panels"]) { | ||
| 104 | int panel_id = | ||
| 105 | AddOrGetPanel(room_obj.name, panel_it.first.as<std::string>()); | ||
| 106 | Panel &panel_obj = panels_[panel_id]; | ||
| 107 | |||
| 108 | if (panel_it.second["colors"]) { | ||
| 109 | if (panel_it.second["colors"].IsScalar()) { | ||
| 110 | panel_obj.colors.push_back( | ||
| 111 | GetColorForString(panel_it.second["colors"].as<std::string>())); | ||
| 112 | } else { | ||
| 113 | for (const auto &color_node : panel_it.second["colors"]) { | ||
| 114 | panel_obj.colors.push_back( | ||
| 115 | GetColorForString(color_node.as<std::string>())); | ||
| 116 | } | ||
| 117 | } | ||
| 118 | } | ||
| 119 | |||
| 120 | if (panel_it.second["required_room"]) { | ||
| 121 | if (panel_it.second["required_room"].IsScalar()) { | ||
| 122 | panel_obj.required_rooms.push_back(AddOrGetRoom( | ||
| 123 | panel_it.second["required_room"].as<std::string>())); | ||
| 124 | } else { | ||
| 125 | for (const auto &rr_node : panel_it.second["required_room"]) { | ||
| 126 | panel_obj.required_rooms.push_back( | ||
| 127 | AddOrGetRoom(rr_node.as<std::string>())); | ||
| 128 | } | ||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 132 | if (panel_it.second["required_door"]) { | ||
| 133 | if (panel_it.second["required_door"].IsMap()) { | ||
| 134 | std::string rd_room = room_obj.name; | ||
| 135 | if (panel_it.second["required_door"]["room"]) { | ||
| 136 | rd_room = | ||
| 137 | panel_it.second["required_door"]["room"].as<std::string>(); | ||
| 138 | } | ||
| 139 | |||
| 140 | panel_obj.required_doors.push_back(AddOrGetDoor( | ||
| 141 | rd_room, | ||
| 142 | panel_it.second["required_door"]["door"].as<std::string>())); | ||
| 143 | } else { | ||
| 144 | for (const auto &rr_node : panel_it.second["required_door"]) { | ||
| 145 | std::string rd_room = room_obj.name; | ||
| 146 | if (rr_node["room"]) { | ||
| 147 | rd_room = rr_node["room"].as<std::string>(); | ||
| 148 | } | ||
| 149 | |||
| 150 | panel_obj.required_doors.push_back( | ||
| 151 | AddOrGetDoor(rd_room, rr_node["door"].as<std::string>())); | ||
| 152 | } | ||
| 153 | } | ||
| 154 | } | ||
| 155 | |||
| 156 | if (panel_it.second["check"]) { | ||
| 157 | panel_obj.check = panel_it.second["check"].as<bool>(); | ||
| 158 | } | ||
| 159 | |||
| 160 | if (panel_it.second["achievement"]) { | ||
| 161 | panel_obj.achievement = panel_it.second["achievement"].as<bool>(); | ||
| 162 | |||
| 163 | if (panel_obj.achievement) { | ||
| 164 | achievement_panels_.push_back(panel_id); | ||
| 165 | } | ||
| 166 | } | ||
| 167 | |||
| 168 | if (panel_it.second["exclude_reduce"]) { | ||
| 169 | panel_obj.exclude_reduce = | ||
| 170 | panel_it.second["exclude_reduce"].as<bool>(); | ||
| 171 | } | ||
| 172 | } | ||
| 173 | } | ||
| 174 | |||
| 175 | if (room_it.second["doors"]) { | ||
| 176 | for (const auto &door_it : room_it.second["doors"]) { | ||
| 177 | int door_id = | ||
| 178 | AddOrGetDoor(room_obj.name, door_it.first.as<std::string>()); | ||
| 179 | Door &door_obj = doors_[door_id]; | ||
| 180 | |||
| 181 | bool has_external_panels = false; | ||
| 182 | std::vector<std::string> panel_names; | ||
| 183 | |||
| 184 | for (const auto &panel_node : door_it.second["panels"]) { | ||
| 185 | if (panel_node.IsScalar()) { | ||
| 186 | panel_names.push_back(panel_node.as<std::string>()); | ||
| 187 | door_obj.panels.push_back( | ||
| 188 | AddOrGetPanel(room_obj.name, panel_node.as<std::string>())); | ||
| 189 | } else { | ||
| 190 | has_external_panels = true; | ||
| 191 | panel_names.push_back(panel_node["panel"].as<std::string>()); | ||
| 192 | door_obj.panels.push_back( | ||
| 193 | AddOrGetPanel(panel_node["room"].as<std::string>(), | ||
| 194 | panel_node["panel"].as<std::string>())); | ||
| 195 | } | ||
| 196 | } | ||
| 197 | |||
| 198 | if (door_it.second["skip_location"]) { | ||
| 199 | door_obj.skip_location = door_it.second["skip_location"].as<bool>(); | ||
| 200 | } | ||
| 201 | |||
| 202 | if (door_it.second["skip_item"]) { | ||
| 203 | door_obj.skip_item = door_it.second["skip_item"].as<bool>(); | ||
| 204 | } | ||
| 205 | |||
| 206 | if (door_it.second["event"]) { | ||
| 207 | door_obj.skip_location = door_it.second["event"].as<bool>(); | ||
| 208 | door_obj.skip_item = door_it.second["event"].as<bool>(); | ||
| 209 | } | ||
| 210 | |||
| 211 | if (door_it.second["item_name"]) { | ||
| 212 | door_obj.item_name = door_it.second["item_name"].as<std::string>(); | ||
| 213 | } else if (!door_it.second["skip_item"] && !door_it.second["event"]) { | ||
| 214 | door_obj.item_name = room_obj.name + " - " + door_obj.name; | ||
| 215 | } | ||
| 216 | |||
| 217 | if (door_it.second["group"]) { | ||
| 218 | door_obj.group_name = door_it.second["group"].as<std::string>(); | ||
| 219 | } | ||
| 220 | |||
| 221 | if (door_it.second["location_name"]) { | ||
| 222 | door_obj.location_name = | ||
| 223 | door_it.second["location_name"].as<std::string>(); | ||
| 224 | } else if (!door_it.second["skip_location"] && | ||
| 225 | !door_it.second["event"]) { | ||
| 226 | if (has_external_panels) { | ||
| 227 | std::cout | ||
| 228 | << room_obj.name << " - " << door_obj.name | ||
| 229 | << " has panels from other rooms but does not have an explicit " | ||
| 230 | "location name and is not marked skip_location or event" | ||
| 231 | << std::endl; | ||
| 232 | } | ||
| 233 | |||
| 234 | door_obj.location_name = | ||
| 235 | room_obj.name + " - " + hatkirby::implode(panel_names, ", "); | ||
| 236 | } | ||
| 237 | |||
| 238 | if (door_it.second["include_reduce"]) { | ||
| 239 | door_obj.exclude_reduce = | ||
| 240 | !door_it.second["include_reduce"].as<bool>(); | ||
| 241 | } | ||
| 242 | } | ||
| 243 | } | ||
| 244 | |||
| 245 | if (room_it.second["paintings"]) { | ||
| 246 | for (const auto &painting : room_it.second["paintings"]) { | ||
| 247 | std::string painting_id = painting["id"].as<std::string>(); | ||
| 248 | room_by_painting_[painting_id] = room_id; | ||
| 249 | |||
| 250 | if (!painting["exit_only"] || !painting["exit_only"].as<bool>()) { | ||
| 251 | PaintingExit painting_exit; | ||
| 252 | painting_exit.id = painting_id; | ||
| 253 | |||
| 254 | if (painting["required_door"]) { | ||
| 255 | std::string rd_room = room_obj.name; | ||
| 256 | if (painting["required_door"]["room"]) { | ||
| 257 | rd_room = painting["required_door"]["room"].as<std::string>(); | ||
| 258 | } | ||
| 259 | |||
| 260 | painting_exit.door = AddOrGetDoor( | ||
| 261 | rd_room, painting["required_door"]["door"].as<std::string>()); | ||
| 262 | } | ||
| 263 | |||
| 264 | room_obj.paintings.push_back(painting_exit); | ||
| 265 | } | ||
| 266 | } | ||
| 267 | } | ||
| 268 | |||
| 269 | if (room_it.second["progression"]) { | ||
| 270 | for (const auto &progression_it : room_it.second["progression"]) { | ||
| 271 | std::string progressive_item_name = | ||
| 272 | progression_it.first.as<std::string>(); | ||
| 273 | |||
| 274 | int index = 1; | ||
| 275 | for (const auto &stage : progression_it.second) { | ||
| 276 | int door_id = -1; | ||
| 277 | |||
| 278 | if (stage.IsScalar()) { | ||
| 279 | door_id = AddOrGetDoor(room_obj.name, stage.as<std::string>()); | ||
| 280 | } else { | ||
| 281 | door_id = AddOrGetDoor(stage["room"].as<std::string>(), | ||
| 282 | stage["door"].as<std::string>()); | ||
| 283 | } | ||
| 284 | |||
| 285 | doors_[door_id].progressives.push_back( | ||
| 286 | {.item_name = progressive_item_name, .quantity = index}); | ||
| 287 | index++; | ||
| 288 | } | ||
| 289 | } | ||
| 290 | } | ||
| 291 | } | ||
| 292 | |||
| 293 | map_areas_.reserve(areas_config.size()); | ||
| 294 | |||
| 295 | std::map<std::string, int> fold_areas; | ||
| 296 | for (const auto &area_it : areas_config) { | ||
| 297 | if (area_it.second["map"]) { | ||
| 298 | int area_id = AddOrGetArea(area_it.first.as<std::string>()); | ||
| 299 | MapArea &area_obj = map_areas_[area_id]; | ||
| 300 | area_obj.map_x = area_it.second["map"][0].as<int>(); | ||
| 301 | area_obj.map_y = area_it.second["map"][1].as<int>(); | ||
| 302 | } else if (area_it.second["fold_into"]) { | ||
| 303 | fold_areas[area_it.first.as<std::string>()] = | ||
| 304 | AddOrGetArea(area_it.second["fold_into"].as<std::string>()); | ||
| 305 | } | ||
| 306 | } | ||
| 307 | |||
| 308 | for (const Panel &panel : panels_) { | ||
| 309 | if (panel.check) { | ||
| 310 | int room_id = panel.room; | ||
| 311 | std::string room_name = rooms_[room_id].name; | ||
| 312 | |||
| 313 | std::string area_name = room_name; | ||
| 314 | if (fold_areas.count(room_name)) { | ||
| 315 | int fold_area_id = fold_areas[room_name]; | ||
| 316 | area_name = map_areas_[fold_area_id].name; | ||
| 317 | } | ||
| 318 | |||
| 319 | int area_id = AddOrGetArea(area_name); | ||
| 320 | MapArea &map_area = map_areas_[area_id]; | ||
| 321 | // room field should be the original room ID | ||
| 322 | map_area.locations.push_back( | ||
| 323 | {.name = panel.name, | ||
| 324 | .ap_location_name = room_name + " - " + panel.name, | ||
| 325 | .room = panel.room, | ||
| 326 | .panels = {panel.id}}); | ||
| 327 | } | ||
| 328 | } | ||
| 329 | |||
| 330 | for (const Door &door : doors_) { | ||
| 331 | if (!door.skip_location) { | ||
| 332 | int room_id = door.room; | ||
| 333 | std::string area_name = rooms_[room_id].name; | ||
| 334 | std::string section_name; | ||
| 335 | |||
| 336 | size_t divider_pos = door.location_name.find(" - "); | ||
| 337 | if (divider_pos == std::string::npos) { | ||
| 338 | section_name = door.location_name; | ||
| 339 | } else { | ||
| 340 | area_name = door.location_name.substr(0, divider_pos); | ||
| 341 | section_name = door.location_name.substr(divider_pos + 3); | ||
| 342 | } | ||
| 343 | |||
| 344 | if (fold_areas.count(area_name)) { | ||
| 345 | int fold_area_id = fold_areas[area_name]; | ||
| 346 | area_name = map_areas_[fold_area_id].name; | ||
| 347 | } | ||
| 348 | |||
| 349 | int area_id = AddOrGetArea(area_name); | ||
| 350 | MapArea &map_area = map_areas_[area_id]; | ||
| 351 | // room field should be the original room ID | ||
| 352 | map_area.locations.push_back({.name = section_name, | ||
| 353 | .ap_location_name = door.location_name, | ||
| 354 | .room = door.room, | ||
| 355 | .panels = door.panels}); | ||
| 356 | } | ||
| 357 | } | ||
| 358 | } | ||
| 359 | |||
| 360 | int GameData::AddOrGetRoom(std::string room) { | ||
| 361 | if (!room_by_id_.count(room)) { | ||
| 362 | room_by_id_[room] = rooms_.size(); | ||
| 363 | rooms_.push_back({.name = room}); | ||
| 364 | } | ||
| 365 | |||
| 366 | return room_by_id_[room]; | ||
| 367 | } | ||
| 368 | |||
| 369 | int GameData::AddOrGetDoor(std::string room, std::string door) { | ||
| 370 | std::string full_name = room + " - " + door; | ||
| 371 | |||
| 372 | if (!door_by_id_.count(full_name)) { | ||
| 373 | door_by_id_[full_name] = doors_.size(); | ||
| 374 | doors_.push_back({.room = AddOrGetRoom(room), .name = door}); | ||
| 375 | } | ||
| 376 | |||
| 377 | return door_by_id_[full_name]; | ||
| 378 | } | ||
| 379 | |||
| 380 | int GameData::AddOrGetPanel(std::string room, std::string panel) { | ||
| 381 | std::string full_name = room + " - " + panel; | ||
| 382 | |||
| 383 | if (!panel_by_id_.count(full_name)) { | ||
| 384 | int panel_id = panels_.size(); | ||
| 385 | panel_by_id_[full_name] = panel_id; | ||
| 386 | panels_.push_back( | ||
| 387 | {.id = panel_id, .room = AddOrGetRoom(room), .name = panel}); | ||
| 388 | } | ||
| 389 | |||
| 390 | return panel_by_id_[full_name]; | ||
| 391 | } | ||
| 392 | |||
| 393 | int GameData::AddOrGetArea(std::string area) { | ||
| 394 | if (!area_by_id_.count(area)) { | ||
| 395 | int area_id = map_areas_.size(); | ||
| 396 | area_by_id_[area] = area_id; | ||
| 397 | map_areas_.push_back({.id = area_id, .name = area}); | ||
| 398 | } | ||
| 399 | |||
| 400 | return area_by_id_[area]; | ||
| 401 | } | ||
| 402 | |||
| 403 | const GameData &GetGameData() { | ||
| 404 | static GameData *instance = new GameData(); | ||
| 405 | return *instance; | ||
| 406 | } | ||
| diff --git a/src/game_data.h b/src/game_data.h new file mode 100644 index 0000000..0cc7a7b --- /dev/null +++ b/src/game_data.h | |||
| @@ -0,0 +1,135 @@ | |||
| 1 | #ifndef GAME_DATA_H_9C42AC51 | ||
| 2 | #define GAME_DATA_H_9C42AC51 | ||
| 3 | |||
| 4 | #include <map> | ||
| 5 | #include <optional> | ||
| 6 | #include <string> | ||
| 7 | #include <vector> | ||
| 8 | |||
| 9 | enum class LingoColor { | ||
| 10 | kNone, | ||
| 11 | kBlack, | ||
| 12 | kRed, | ||
| 13 | kBlue, | ||
| 14 | kYellow, | ||
| 15 | kGreen, | ||
| 16 | kOrange, | ||
| 17 | kPurple, | ||
| 18 | kBrown, | ||
| 19 | kGray | ||
| 20 | }; | ||
| 21 | |||
| 22 | struct Panel { | ||
| 23 | int id; | ||
| 24 | int room; | ||
| 25 | std::string name; | ||
| 26 | std::vector<LingoColor> colors; | ||
| 27 | std::vector<int> required_rooms; | ||
| 28 | std::vector<int> required_doors; | ||
| 29 | bool check = false; | ||
| 30 | bool exclude_reduce = false; | ||
| 31 | bool achievement = false; | ||
| 32 | }; | ||
| 33 | |||
| 34 | struct ProgressiveRequirement { | ||
| 35 | std::string item_name; | ||
| 36 | int quantity = 0; | ||
| 37 | }; | ||
| 38 | |||
| 39 | struct Door { | ||
| 40 | int room; | ||
| 41 | std::string name; | ||
| 42 | std::string location_name; | ||
| 43 | std::string item_name; | ||
| 44 | std::string group_name; | ||
| 45 | bool skip_location = false; | ||
| 46 | bool skip_item = false; | ||
| 47 | std::vector<int> panels; | ||
| 48 | bool exclude_reduce = true; | ||
| 49 | std::vector<ProgressiveRequirement> progressives; | ||
| 50 | }; | ||
| 51 | |||
| 52 | struct Exit { | ||
| 53 | int destination_room; | ||
| 54 | std::optional<int> door; | ||
| 55 | bool painting = false; | ||
| 56 | }; | ||
| 57 | |||
| 58 | struct PaintingExit { | ||
| 59 | std::string id; | ||
| 60 | std::optional<int> door; | ||
| 61 | }; | ||
| 62 | |||
| 63 | struct Room { | ||
| 64 | std::string name; | ||
| 65 | std::vector<Exit> exits; | ||
| 66 | std::vector<PaintingExit> paintings; | ||
| 67 | }; | ||
| 68 | |||
| 69 | struct Location { | ||
| 70 | std::string name; | ||
| 71 | std::string ap_location_name; | ||
| 72 | int room; | ||
| 73 | std::vector<int> panels; | ||
| 74 | }; | ||
| 75 | |||
| 76 | struct MapArea { | ||
| 77 | int id; | ||
| 78 | std::string name; | ||
| 79 | std::vector<Location> locations; | ||
| 80 | int map_x; | ||
| 81 | int map_y; | ||
| 82 | }; | ||
| 83 | |||
| 84 | class GameData { | ||
| 85 | public: | ||
| 86 | GameData(); | ||
| 87 | |||
| 88 | const std::vector<MapArea>& GetMapAreas() const { return map_areas_; } | ||
| 89 | |||
| 90 | const MapArea& GetMapArea(int id) const { return map_areas_.at(id); } | ||
| 91 | |||
| 92 | int GetRoomByName(const std::string& name) const { | ||
| 93 | return room_by_id_.at(name); | ||
| 94 | } | ||
| 95 | |||
| 96 | const Room& GetRoom(int room_id) const { return rooms_.at(room_id); } | ||
| 97 | |||
| 98 | const std::vector<Door>& GetDoors() const { return doors_; } | ||
| 99 | |||
| 100 | const Door& GetDoor(int door_id) const { return doors_.at(door_id); } | ||
| 101 | |||
| 102 | const Panel& GetPanel(int panel_id) const { return panels_.at(panel_id); } | ||
| 103 | |||
| 104 | int GetRoomForPainting(const std::string& painting_id) const { | ||
| 105 | return room_by_painting_.at(painting_id); | ||
| 106 | } | ||
| 107 | |||
| 108 | const std::vector<int>& GetAchievementPanels() const { | ||
| 109 | return achievement_panels_; | ||
| 110 | } | ||
| 111 | |||
| 112 | private: | ||
| 113 | int AddOrGetRoom(std::string room); | ||
| 114 | int AddOrGetDoor(std::string room, std::string door); | ||
| 115 | int AddOrGetPanel(std::string room, std::string panel); | ||
| 116 | int AddOrGetArea(std::string area); | ||
| 117 | |||
| 118 | std::vector<Room> rooms_; | ||
| 119 | std::vector<Door> doors_; | ||
| 120 | std::vector<Panel> panels_; | ||
| 121 | std::vector<MapArea> map_areas_; | ||
| 122 | |||
| 123 | std::map<std::string, int> room_by_id_; | ||
| 124 | std::map<std::string, int> door_by_id_; | ||
| 125 | std::map<std::string, int> panel_by_id_; | ||
| 126 | std::map<std::string, int> area_by_id_; | ||
| 127 | |||
| 128 | std::map<std::string, int> room_by_painting_; | ||
| 129 | |||
| 130 | std::vector<int> achievement_panels_; | ||
| 131 | }; | ||
| 132 | |||
| 133 | const GameData& GetGameData(); | ||
| 134 | |||
| 135 | #endif /* end of include guard: GAME_DATA_H_9C42AC51 */ | ||
| diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..fe9aceb --- /dev/null +++ b/src/main.cpp | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | #include <wx/wxprec.h> | ||
| 2 | |||
| 3 | #ifndef WX_PRECOMP | ||
| 4 | #include <wx/wx.h> | ||
| 5 | #endif | ||
| 6 | |||
| 7 | #include "tracker_config.h" | ||
| 8 | #include "tracker_frame.h" | ||
| 9 | |||
| 10 | class TrackerApp : public wxApp { | ||
| 11 | public: | ||
| 12 | virtual bool OnInit() { | ||
| 13 | GetTrackerConfig().Load(); | ||
| 14 | |||
| 15 | TrackerFrame *frame = new TrackerFrame(); | ||
| 16 | frame->Show(true); | ||
| 17 | return true; | ||
| 18 | } | ||
| 19 | }; | ||
| 20 | |||
| 21 | wxIMPLEMENT_APP(TrackerApp); | ||
| diff --git a/src/tracker_config.cpp b/src/tracker_config.cpp new file mode 100644 index 0000000..96bb60a --- /dev/null +++ b/src/tracker_config.cpp | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | #include "tracker_config.h" | ||
| 2 | |||
| 3 | #include <fstream> | ||
| 4 | #include <yaml-cpp/yaml.h> | ||
| 5 | |||
| 6 | constexpr const char* CONFIG_FILE_NAME = "config.yaml"; | ||
| 7 | |||
| 8 | void TrackerConfig::Load() { | ||
| 9 | try { | ||
| 10 | YAML::Node file = YAML::LoadFile(CONFIG_FILE_NAME); | ||
| 11 | |||
| 12 | ap_server = file["ap_server"].as<std::string>(); | ||
| 13 | ap_player = file["ap_player"].as<std::string>(); | ||
| 14 | ap_password = file["ap_password"].as<std::string>(); | ||
| 15 | } catch (const std::exception&) { | ||
| 16 | // It's fine if the file can't be loaded. | ||
| 17 | } | ||
| 18 | } | ||
| 19 | |||
| 20 | void TrackerConfig::Save() { | ||
| 21 | YAML::Node output; | ||
| 22 | output["ap_server"] = ap_server; | ||
| 23 | output["ap_player"] = ap_player; | ||
| 24 | output["ap_password"] = ap_password; | ||
| 25 | |||
| 26 | std::ofstream filewriter(CONFIG_FILE_NAME); | ||
| 27 | filewriter << output; | ||
| 28 | } | ||
| 29 | |||
| 30 | TrackerConfig& GetTrackerConfig() { | ||
| 31 | static TrackerConfig* instance = new TrackerConfig(); | ||
| 32 | return *instance; | ||
| 33 | } | ||
| diff --git a/src/tracker_config.h b/src/tracker_config.h new file mode 100644 index 0000000..e2e6cd7 --- /dev/null +++ b/src/tracker_config.h | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | #ifndef TRACKER_CONFIG_H_36CDD648 | ||
| 2 | #define TRACKER_CONFIG_H_36CDD648 | ||
| 3 | |||
| 4 | #include <string> | ||
| 5 | |||
| 6 | class TrackerConfig { | ||
| 7 | public: | ||
| 8 | void Load(); | ||
| 9 | |||
| 10 | void Save(); | ||
| 11 | |||
| 12 | std::string ap_server; | ||
| 13 | std::string ap_player; | ||
| 14 | std::string ap_password; | ||
| 15 | }; | ||
| 16 | |||
| 17 | TrackerConfig& GetTrackerConfig(); | ||
| 18 | |||
| 19 | #endif /* end of include guard: TRACKER_CONFIG_H_36CDD648 */ | ||
| diff --git a/src/tracker_frame.cpp b/src/tracker_frame.cpp new file mode 100644 index 0000000..2a862a5 --- /dev/null +++ b/src/tracker_frame.cpp | |||
| @@ -0,0 +1,86 @@ | |||
| 1 | #include "tracker_frame.h" | ||
| 2 | |||
| 3 | #include "ap_state.h" | ||
| 4 | #include "connection_dialog.h" | ||
| 5 | #include "tracker_config.h" | ||
| 6 | #include "tracker_panel.h" | ||
| 7 | |||
| 8 | enum TrackerFrameIds { ID_CONNECT = 1 }; | ||
| 9 | |||
| 10 | wxDEFINE_EVENT(STATE_CHANGED, wxCommandEvent); | ||
| 11 | wxDEFINE_EVENT(STATUS_CHANGED, wxCommandEvent); | ||
| 12 | |||
| 13 | TrackerFrame::TrackerFrame() | ||
| 14 | : wxFrame(nullptr, wxID_ANY, "Lingo Archipelago Tracker", wxDefaultPosition, | ||
| 15 | wxDefaultSize, wxDEFAULT_FRAME_STYLE | wxFULL_REPAINT_ON_RESIZE) { | ||
| 16 | ::wxInitAllImageHandlers(); | ||
| 17 | |||
| 18 | AP_SetTrackerFrame(this); | ||
| 19 | |||
| 20 | SetSize(1280, 728); | ||
| 21 | |||
| 22 | wxMenu *menuFile = new wxMenu(); | ||
| 23 | menuFile->Append(ID_CONNECT, "&Connect"); | ||
| 24 | menuFile->Append(wxID_EXIT); | ||
| 25 | |||
| 26 | wxMenu *menuHelp = new wxMenu(); | ||
| 27 | menuHelp->Append(wxID_ABOUT); | ||
| 28 | |||
| 29 | wxMenuBar *menuBar = new wxMenuBar(); | ||
| 30 | menuBar->Append(menuFile, "&File"); | ||
| 31 | menuBar->Append(menuHelp, "&Help"); | ||
| 32 | |||
| 33 | SetMenuBar(menuBar); | ||
| 34 | |||
| 35 | CreateStatusBar(); | ||
| 36 | SetStatusText("Not connected to Archipelago."); | ||
| 37 | |||
| 38 | Bind(wxEVT_MENU, &TrackerFrame::OnAbout, this, wxID_ABOUT); | ||
| 39 | Bind(wxEVT_MENU, &TrackerFrame::OnExit, this, wxID_EXIT); | ||
| 40 | Bind(wxEVT_MENU, &TrackerFrame::OnConnect, this, ID_CONNECT); | ||
| 41 | Bind(STATE_CHANGED, &TrackerFrame::OnStateChanged, this); | ||
| 42 | Bind(STATUS_CHANGED, &TrackerFrame::OnStatusChanged, this); | ||
| 43 | |||
| 44 | tracker_panel_ = new TrackerPanel(this); | ||
| 45 | } | ||
| 46 | |||
| 47 | void TrackerFrame::SetStatusMessage(std::string message) { | ||
| 48 | wxCommandEvent *event = new wxCommandEvent(STATUS_CHANGED); | ||
| 49 | event->SetString(message.c_str()); | ||
| 50 | |||
| 51 | QueueEvent(event); | ||
| 52 | } | ||
| 53 | |||
| 54 | void TrackerFrame::UpdateIndicators() { | ||
| 55 | QueueEvent(new wxCommandEvent(STATE_CHANGED)); | ||
| 56 | } | ||
| 57 | |||
| 58 | void TrackerFrame::OnAbout(wxCommandEvent &event) { | ||
| 59 | wxMessageBox("Lingo Archipelago Tracker by hatkirby", | ||
| 60 | "About lingo-ap-tracker", wxOK | wxICON_INFORMATION); | ||
| 61 | } | ||
| 62 | |||
| 63 | void TrackerFrame::OnExit(wxCommandEvent &event) { Close(true); } | ||
| 64 | |||
| 65 | void TrackerFrame::OnConnect(wxCommandEvent &event) { | ||
| 66 | ConnectionDialog dlg; | ||
| 67 | |||
| 68 | if (dlg.ShowModal() == wxID_OK) { | ||
| 69 | GetTrackerConfig().ap_server = dlg.GetServerValue(); | ||
| 70 | GetTrackerConfig().ap_player = dlg.GetPlayerValue(); | ||
| 71 | GetTrackerConfig().ap_password = dlg.GetPasswordValue(); | ||
| 72 | GetTrackerConfig().Save(); | ||
| 73 | |||
| 74 | AP_Connect(dlg.GetServerValue(), dlg.GetPlayerValue(), | ||
| 75 | dlg.GetPasswordValue()); | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | void TrackerFrame::OnStateChanged(wxCommandEvent &event) { | ||
| 80 | tracker_panel_->UpdateIndicators(); | ||
| 81 | Refresh(); | ||
| 82 | } | ||
| 83 | |||
| 84 | void TrackerFrame::OnStatusChanged(wxCommandEvent &event) { | ||
| 85 | SetStatusText(event.GetString()); | ||
| 86 | } | ||
| diff --git a/src/tracker_frame.h b/src/tracker_frame.h new file mode 100644 index 0000000..6f4e4fc --- /dev/null +++ b/src/tracker_frame.h | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | #ifndef TRACKER_FRAME_H_86BD8DFB | ||
| 2 | #define TRACKER_FRAME_H_86BD8DFB | ||
| 3 | |||
| 4 | #include <wx/wxprec.h> | ||
| 5 | |||
| 6 | #ifndef WX_PRECOMP | ||
| 7 | #include <wx/wx.h> | ||
| 8 | #endif | ||
| 9 | |||
| 10 | class TrackerPanel; | ||
| 11 | |||
| 12 | wxDECLARE_EVENT(STATE_CHANGED, wxCommandEvent); | ||
| 13 | wxDECLARE_EVENT(STATUS_CHANGED, wxCommandEvent); | ||
| 14 | |||
| 15 | class TrackerFrame : public wxFrame { | ||
| 16 | public: | ||
| 17 | TrackerFrame(); | ||
| 18 | |||
| 19 | void SetStatusMessage(std::string message); | ||
| 20 | |||
| 21 | void UpdateIndicators(); | ||
| 22 | |||
| 23 | private: | ||
| 24 | void OnExit(wxCommandEvent &event); | ||
| 25 | void OnAbout(wxCommandEvent &event); | ||
| 26 | void OnConnect(wxCommandEvent &event); | ||
| 27 | void OnStateChanged(wxCommandEvent &event); | ||
| 28 | void OnStatusChanged(wxCommandEvent &event); | ||
| 29 | |||
| 30 | TrackerPanel *tracker_panel_; | ||
| 31 | }; | ||
| 32 | |||
| 33 | #endif /* end of include guard: TRACKER_FRAME_H_86BD8DFB */ | ||
| diff --git a/src/tracker_panel.cpp b/src/tracker_panel.cpp new file mode 100644 index 0000000..0e0569b --- /dev/null +++ b/src/tracker_panel.cpp | |||
| @@ -0,0 +1,149 @@ | |||
| 1 | #include "tracker_panel.h" | ||
| 2 | |||
| 3 | #include "ap_state.h" | ||
| 4 | #include "area_popup.h" | ||
| 5 | #include "game_data.h" | ||
| 6 | #include "tracker_state.h" | ||
| 7 | |||
| 8 | constexpr int AREA_ACTUAL_SIZE = 64; | ||
| 9 | constexpr int AREA_BORDER_SIZE = 5; | ||
| 10 | constexpr int AREA_EFFECTIVE_SIZE = AREA_ACTUAL_SIZE + AREA_BORDER_SIZE * 2; | ||
| 11 | |||
| 12 | TrackerPanel::TrackerPanel(wxWindow *parent) : wxPanel(parent, wxID_ANY) { | ||
| 13 | map_image_ = wxImage("assets/lingo_map.png", wxBITMAP_TYPE_PNG); | ||
| 14 | if (!map_image_.IsOk()) { | ||
| 15 | return; | ||
| 16 | } | ||
| 17 | |||
| 18 | for (const MapArea &map_area : GetGameData().GetMapAreas()) { | ||
| 19 | AreaIndicator area; | ||
| 20 | area.area_id = map_area.id; | ||
| 21 | |||
| 22 | area.popup = new AreaPopup(this, map_area.id); | ||
| 23 | area.popup->SetPosition({0, 0}); | ||
| 24 | |||
| 25 | areas_.push_back(area); | ||
| 26 | } | ||
| 27 | |||
| 28 | Redraw(); | ||
| 29 | |||
| 30 | Bind(wxEVT_PAINT, &TrackerPanel::OnPaint, this); | ||
| 31 | Bind(wxEVT_MOTION, &TrackerPanel::OnMouseMove, this); | ||
| 32 | } | ||
| 33 | |||
| 34 | void TrackerPanel::UpdateIndicators() { | ||
| 35 | Redraw(); | ||
| 36 | |||
| 37 | for (AreaIndicator &area : areas_) { | ||
| 38 | area.popup->UpdateIndicators(); | ||
| 39 | } | ||
| 40 | } | ||
| 41 | |||
| 42 | void TrackerPanel::OnPaint(wxPaintEvent &event) { | ||
| 43 | if (GetSize() != rendered_.GetSize()) { | ||
| 44 | Redraw(); | ||
| 45 | } | ||
| 46 | |||
| 47 | wxPaintDC dc(this); | ||
| 48 | dc.DrawBitmap(rendered_, 0, 0); | ||
| 49 | |||
| 50 | event.Skip(); | ||
| 51 | } | ||
| 52 | |||
| 53 | void TrackerPanel::OnMouseMove(wxMouseEvent &event) { | ||
| 54 | for (AreaIndicator &area : areas_) { | ||
| 55 | if (area.real_x1 <= event.GetX() && event.GetX() < area.real_x2 && | ||
| 56 | area.real_y1 <= event.GetY() && event.GetY() < area.real_y2) { | ||
| 57 | area.popup->Show(); | ||
| 58 | } else { | ||
| 59 | area.popup->Hide(); | ||
| 60 | } | ||
| 61 | } | ||
| 62 | |||
| 63 | event.Skip(); | ||
| 64 | } | ||
| 65 | |||
| 66 | void TrackerPanel::Redraw() { | ||
| 67 | wxSize panel_size = GetSize(); | ||
| 68 | wxSize image_size = map_image_.GetSize(); | ||
| 69 | |||
| 70 | int final_x = 0; | ||
| 71 | int final_y = 0; | ||
| 72 | int final_width = panel_size.GetWidth(); | ||
| 73 | int final_height = panel_size.GetHeight(); | ||
| 74 | |||
| 75 | if (image_size.GetWidth() * panel_size.GetHeight() > | ||
| 76 | panel_size.GetWidth() * image_size.GetHeight()) { | ||
| 77 | final_height = (panel_size.GetWidth() * image_size.GetHeight()) / | ||
| 78 | image_size.GetWidth(); | ||
| 79 | final_y = (panel_size.GetHeight() - final_height) / 2; | ||
| 80 | } else { | ||
| 81 | final_width = (image_size.GetWidth() * panel_size.GetHeight()) / | ||
| 82 | image_size.GetHeight(); | ||
| 83 | final_x = (panel_size.GetWidth() - final_width) / 2; | ||
| 84 | } | ||
| 85 | |||
| 86 | rendered_ = wxBitmap( | ||
| 87 | map_image_.Scale(final_width, final_height, wxIMAGE_QUALITY_NORMAL) | ||
| 88 | .Size(panel_size, {final_x, final_y}, 0, 0, 0)); | ||
| 89 | |||
| 90 | wxMemoryDC dc; | ||
| 91 | dc.SelectObject(rendered_); | ||
| 92 | |||
| 93 | for (AreaIndicator &area : areas_) { | ||
| 94 | const wxBrush *brush_color = wxGREY_BRUSH; | ||
| 95 | |||
| 96 | const MapArea &map_area = GetGameData().GetMapArea(area.area_id); | ||
| 97 | bool has_reachable_unchecked = false; | ||
| 98 | bool has_unreachable_unchecked = false; | ||
| 99 | for (int section_id = 0; section_id < map_area.locations.size(); | ||
| 100 | section_id++) { | ||
| 101 | if (!AP_HasCheckedGameLocation(area.area_id, section_id)) { | ||
| 102 | if (GetTrackerState().IsLocationReachable(area.area_id, section_id)) { | ||
| 103 | has_reachable_unchecked = true; | ||
| 104 | } else { | ||
| 105 | has_unreachable_unchecked = true; | ||
| 106 | } | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | if (has_reachable_unchecked && has_unreachable_unchecked) { | ||
| 111 | brush_color = wxYELLOW_BRUSH; | ||
| 112 | } else if (has_reachable_unchecked) { | ||
| 113 | brush_color = wxGREEN_BRUSH; | ||
| 114 | } else if (has_unreachable_unchecked) { | ||
| 115 | brush_color = wxRED_BRUSH; | ||
| 116 | } | ||
| 117 | |||
| 118 | int real_area_size = | ||
| 119 | final_width * AREA_EFFECTIVE_SIZE / image_size.GetWidth(); | ||
| 120 | int actual_border_size = | ||
| 121 | real_area_size * AREA_BORDER_SIZE / AREA_EFFECTIVE_SIZE; | ||
| 122 | int real_area_x = final_x + (map_area.map_x - (AREA_EFFECTIVE_SIZE / 2)) * | ||
| 123 | final_width / image_size.GetWidth(); | ||
| 124 | int real_area_y = final_y + (map_area.map_y - (AREA_EFFECTIVE_SIZE / 2)) * | ||
| 125 | final_width / image_size.GetWidth(); | ||
| 126 | |||
| 127 | dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, actual_border_size)); | ||
| 128 | dc.SetBrush(*brush_color); | ||
| 129 | dc.DrawRectangle({real_area_x, real_area_y}, | ||
| 130 | {real_area_size, real_area_size}); | ||
| 131 | |||
| 132 | area.real_x1 = real_area_x; | ||
| 133 | area.real_x2 = real_area_x + real_area_size; | ||
| 134 | area.real_y1 = real_area_y; | ||
| 135 | area.real_y2 = real_area_y + real_area_size; | ||
| 136 | |||
| 137 | int popup_x = | ||
| 138 | final_x + map_area.map_x * final_width / image_size.GetWidth(); | ||
| 139 | int popup_y = | ||
| 140 | final_y + map_area.map_y * final_width / image_size.GetWidth(); | ||
| 141 | if (popup_x + area.popup->GetSize().GetWidth() > panel_size.GetWidth()) { | ||
| 142 | popup_x = panel_size.GetWidth() - area.popup->GetSize().GetWidth(); | ||
| 143 | } | ||
| 144 | if (popup_y + area.popup->GetSize().GetHeight() > panel_size.GetHeight()) { | ||
| 145 | popup_y = panel_size.GetHeight() - area.popup->GetSize().GetHeight(); | ||
| 146 | } | ||
| 147 | area.popup->SetPosition({popup_x, popup_y}); | ||
| 148 | } | ||
| 149 | } | ||
| diff --git a/src/tracker_panel.h b/src/tracker_panel.h new file mode 100644 index 0000000..a871f90 --- /dev/null +++ b/src/tracker_panel.h | |||
| @@ -0,0 +1,39 @@ | |||
| 1 | #ifndef TRACKER_PANEL_H_D675A54D | ||
| 2 | #define TRACKER_PANEL_H_D675A54D | ||
| 3 | |||
| 4 | #include <wx/wxprec.h> | ||
| 5 | |||
| 6 | #ifndef WX_PRECOMP | ||
| 7 | #include <wx/wx.h> | ||
| 8 | #endif | ||
| 9 | |||
| 10 | class AreaPopup; | ||
| 11 | |||
| 12 | class TrackerPanel : public wxPanel { | ||
| 13 | public: | ||
| 14 | TrackerPanel(wxWindow *parent); | ||
| 15 | |||
| 16 | void UpdateIndicators(); | ||
| 17 | |||
| 18 | private: | ||
| 19 | struct AreaIndicator { | ||
| 20 | int area_id = -1; | ||
| 21 | AreaPopup *popup = nullptr; | ||
| 22 | int real_x1 = 0; | ||
| 23 | int real_y1 = 0; | ||
| 24 | int real_x2 = 0; | ||
| 25 | int real_y2 = 0; | ||
| 26 | }; | ||
| 27 | |||
| 28 | void OnPaint(wxPaintEvent &event); | ||
| 29 | void OnMouseMove(wxMouseEvent &event); | ||
| 30 | |||
| 31 | void Redraw(); | ||
| 32 | |||
| 33 | wxImage map_image_; | ||
| 34 | wxBitmap rendered_; | ||
| 35 | |||
| 36 | std::vector<AreaIndicator> areas_; | ||
| 37 | }; | ||
| 38 | |||
| 39 | #endif /* end of include guard: TRACKER_PANEL_H_D675A54D */ | ||
| diff --git a/src/tracker_state.cpp b/src/tracker_state.cpp new file mode 100644 index 0000000..858ec3e --- /dev/null +++ b/src/tracker_state.cpp | |||
| @@ -0,0 +1,181 @@ | |||
| 1 | #include "tracker_state.h" | ||
| 2 | |||
| 3 | #include <list> | ||
| 4 | #include <set> | ||
| 5 | |||
| 6 | #include "ap_state.h" | ||
| 7 | #include "game_data.h" | ||
| 8 | |||
| 9 | bool IsDoorReachable_Helper(int door_id, const std::set<int>& reachable_rooms); | ||
| 10 | |||
| 11 | bool IsPanelReachable_Helper(int panel_id, | ||
| 12 | const std::set<int>& reachable_rooms) { | ||
| 13 | const Panel& panel_obj = GetGameData().GetPanel(panel_id); | ||
| 14 | |||
| 15 | if (!reachable_rooms.count(panel_obj.room)) { | ||
| 16 | return false; | ||
| 17 | } | ||
| 18 | |||
| 19 | if (panel_obj.name == "THE MASTER") { | ||
| 20 | int achievements_accessible = 0; | ||
| 21 | |||
| 22 | for (int achieve_id : GetGameData().GetAchievementPanels()) { | ||
| 23 | if (IsPanelReachable_Helper(achieve_id, reachable_rooms)) { | ||
| 24 | achievements_accessible++; | ||
| 25 | |||
| 26 | if (achievements_accessible >= AP_GetMasteryRequirement()) { | ||
| 27 | break; | ||
| 28 | } | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | return (achievements_accessible >= AP_GetMasteryRequirement()); | ||
| 33 | } | ||
| 34 | |||
| 35 | for (int room_id : panel_obj.required_rooms) { | ||
| 36 | if (!reachable_rooms.count(room_id)) { | ||
| 37 | return false; | ||
| 38 | } | ||
| 39 | } | ||
| 40 | |||
| 41 | for (int door_id : panel_obj.required_doors) { | ||
| 42 | if (!IsDoorReachable_Helper(door_id, reachable_rooms)) { | ||
| 43 | return false; | ||
| 44 | } | ||
| 45 | } | ||
| 46 | |||
| 47 | if (AP_IsColorShuffle()) { | ||
| 48 | for (LingoColor color : panel_obj.colors) { | ||
| 49 | if (!AP_HasColorItem(color)) { | ||
| 50 | return false; | ||
| 51 | } | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | return true; | ||
| 56 | } | ||
| 57 | |||
| 58 | bool IsDoorReachable_Helper(int door_id, const std::set<int>& reachable_rooms) { | ||
| 59 | const Door& door_obj = GetGameData().GetDoor(door_id); | ||
| 60 | |||
| 61 | if (AP_GetDoorShuffleMode() == kNO_DOORS || door_obj.skip_item) { | ||
| 62 | if (!reachable_rooms.count(door_obj.room)) { | ||
| 63 | return false; | ||
| 64 | } | ||
| 65 | |||
| 66 | for (int panel_id : door_obj.panels) { | ||
| 67 | if (!IsPanelReachable_Helper(panel_id, reachable_rooms)) { | ||
| 68 | return false; | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | return true; | ||
| 73 | } else if (AP_GetDoorShuffleMode() == kSIMPLE_DOORS && | ||
| 74 | !door_obj.group_name.empty()) { | ||
| 75 | return AP_HasItem(door_obj.group_name); | ||
| 76 | } else { | ||
| 77 | bool has_item = AP_HasItem(door_obj.item_name); | ||
| 78 | |||
| 79 | if (!has_item) { | ||
| 80 | for (const ProgressiveRequirement& prog_req : door_obj.progressives) { | ||
| 81 | if (AP_HasItem(prog_req.item_name, prog_req.quantity)) { | ||
| 82 | has_item = true; | ||
| 83 | break; | ||
| 84 | } | ||
| 85 | } | ||
| 86 | } | ||
| 87 | |||
| 88 | return has_item; | ||
| 89 | } | ||
| 90 | } | ||
| 91 | |||
| 92 | void TrackerState::CalculateState() { | ||
| 93 | reachability_.clear(); | ||
| 94 | |||
| 95 | std::set<int> reachable_rooms; | ||
| 96 | |||
| 97 | std::list<Exit> flood_boundary; | ||
| 98 | flood_boundary.push_back( | ||
| 99 | {.destination_room = GetGameData().GetRoomByName("Menu")}); | ||
| 100 | |||
| 101 | bool reachable_changed = true; | ||
| 102 | while (reachable_changed) { | ||
| 103 | reachable_changed = false; | ||
| 104 | |||
| 105 | std::list<Exit> new_boundary; | ||
| 106 | for (const Exit& room_exit : flood_boundary) { | ||
| 107 | if (reachable_rooms.count(room_exit.destination_room)) { | ||
| 108 | continue; | ||
| 109 | } | ||
| 110 | |||
| 111 | bool valid_transition = false; | ||
| 112 | if (room_exit.door.has_value()) { | ||
| 113 | if (IsDoorReachable_Helper(*room_exit.door, reachable_rooms)) { | ||
| 114 | valid_transition = true; | ||
| 115 | } else if (AP_GetDoorShuffleMode() == kNO_DOORS) { | ||
| 116 | new_boundary.push_back(room_exit); | ||
| 117 | } | ||
| 118 | } else { | ||
| 119 | valid_transition = true; | ||
| 120 | } | ||
| 121 | |||
| 122 | if (valid_transition) { | ||
| 123 | reachable_rooms.insert(room_exit.destination_room); | ||
| 124 | reachable_changed = true; | ||
| 125 | |||
| 126 | const Room& room_obj = | ||
| 127 | GetGameData().GetRoom(room_exit.destination_room); | ||
| 128 | for (const Exit& out_edge : room_obj.exits) { | ||
| 129 | if (!out_edge.painting || !AP_IsPaintingShuffle()) { | ||
| 130 | new_boundary.push_back(out_edge); | ||
| 131 | } | ||
| 132 | } | ||
| 133 | |||
| 134 | if (AP_IsPaintingShuffle()) { | ||
| 135 | for (const PaintingExit& out_edge : room_obj.paintings) { | ||
| 136 | if (AP_GetPaintingMapping().count(out_edge.id)) { | ||
| 137 | Exit painting_exit; | ||
| 138 | painting_exit.destination_room = GetGameData().GetRoomForPainting( | ||
| 139 | AP_GetPaintingMapping().at(out_edge.id)); | ||
| 140 | painting_exit.door = out_edge.door; | ||
| 141 | |||
| 142 | new_boundary.push_back(painting_exit); | ||
| 143 | } | ||
| 144 | } | ||
| 145 | } | ||
| 146 | } | ||
| 147 | } | ||
| 148 | |||
| 149 | flood_boundary = new_boundary; | ||
| 150 | } | ||
| 151 | |||
| 152 | for (const MapArea& map_area : GetGameData().GetMapAreas()) { | ||
| 153 | for (int section_id = 0; section_id < map_area.locations.size(); | ||
| 154 | section_id++) { | ||
| 155 | const Location& location_section = map_area.locations.at(section_id); | ||
| 156 | bool reachable = reachable_rooms.count(location_section.room); | ||
| 157 | if (reachable) { | ||
| 158 | for (int panel_id : location_section.panels) { | ||
| 159 | reachable &= IsPanelReachable_Helper(panel_id, reachable_rooms); | ||
| 160 | } | ||
| 161 | } | ||
| 162 | |||
| 163 | reachability_[{map_area.id, section_id}] = reachable; | ||
| 164 | } | ||
| 165 | } | ||
| 166 | } | ||
| 167 | |||
| 168 | bool TrackerState::IsLocationReachable(int area_id, int section_id) { | ||
| 169 | std::tuple<int, int> key = {area_id, section_id}; | ||
| 170 | |||
| 171 | if (reachability_.count(key)) { | ||
| 172 | return reachability_.at(key); | ||
| 173 | } else { | ||
| 174 | return false; | ||
| 175 | } | ||
| 176 | } | ||
| 177 | |||
| 178 | TrackerState& GetTrackerState() { | ||
| 179 | static TrackerState* instance = new TrackerState(); | ||
| 180 | return *instance; | ||
| 181 | } | ||
| diff --git a/src/tracker_state.h b/src/tracker_state.h new file mode 100644 index 0000000..879e6f2 --- /dev/null +++ b/src/tracker_state.h | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | #ifndef TRACKER_STATE_H_8639BC90 | ||
| 2 | #define TRACKER_STATE_H_8639BC90 | ||
| 3 | |||
| 4 | #include <map> | ||
| 5 | #include <tuple> | ||
| 6 | |||
| 7 | class TrackerState { | ||
| 8 | public: | ||
| 9 | void CalculateState(); | ||
| 10 | |||
| 11 | bool IsLocationReachable(int area_id, int section_id); | ||
| 12 | |||
| 13 | private: | ||
| 14 | std::map<std::tuple<int, int>, bool> reachability_; | ||
| 15 | }; | ||
| 16 | |||
| 17 | TrackerState& GetTrackerState(); | ||
| 18 | |||
| 19 | #endif /* end of include guard: TRACKER_STATE_H_8639BC90 */ | ||
