////////////////////////////////////////////////////////////////////// // This file is part of Remere's Map Editor ////////////////////////////////////////////////////////////////////// // Remere's Map Editor is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Remere's Map Editor is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . ////////////////////////////////////////////////////////////////////// #include "main.h" #include "application.h" #include "sprites.h" #include "editor.h" #include "common_windows.h" #include "palette_window.h" #include "preferences.h" #include "result_window.h" #include "minimap_window.h" #include "about_window.h" #include "main_menubar.h" #include "updater.h" #include "artprovider.h" #include "materials.h" #include "map.h" #include "complexitem.h" #include "monster.h" #include "npc.h" #include "lua/lua_script_manager.h" #include "../brushes/icon/rme_icon.xpm" BEGIN_EVENT_TABLE(MainFrame, wxFrame) EVT_CLOSE(MainFrame::OnExit) // Update check complete #ifdef _USE_UPDATER_ EVT_ON_UPDATE_CHECK_FINISHED(wxID_ANY, MainFrame::OnUpdateReceived) #endif EVT_ON_UPDATE_MENUS(wxID_ANY, MainFrame::OnUpdateMenus) EVT_ON_UPDATE_ACTIONS(wxID_ANY, MainFrame::OnUpdateActions) // Idle event handler EVT_IDLE(MainFrame::OnIdle) END_EVENT_TABLE() BEGIN_EVENT_TABLE(MapWindow, wxPanel) EVT_SIZE(MapWindow::OnSize) EVT_COMMAND_SCROLL_TOP(MAP_WINDOW_HSCROLL, MapWindow::OnScroll) EVT_COMMAND_SCROLL_BOTTOM(MAP_WINDOW_HSCROLL, MapWindow::OnScroll) EVT_COMMAND_SCROLL_THUMBTRACK(MAP_WINDOW_HSCROLL, MapWindow::OnScroll) EVT_COMMAND_SCROLL_LINEUP(MAP_WINDOW_HSCROLL, MapWindow::OnScrollLineUp) EVT_COMMAND_SCROLL_LINEDOWN(MAP_WINDOW_HSCROLL, MapWindow::OnScrollLineDown) EVT_COMMAND_SCROLL_PAGEUP(MAP_WINDOW_HSCROLL, MapWindow::OnScrollPageUp) EVT_COMMAND_SCROLL_PAGEDOWN(MAP_WINDOW_HSCROLL, MapWindow::OnScrollPageDown) EVT_COMMAND_SCROLL_TOP(MAP_WINDOW_VSCROLL, MapWindow::OnScroll) EVT_COMMAND_SCROLL_BOTTOM(MAP_WINDOW_VSCROLL, MapWindow::OnScroll) EVT_COMMAND_SCROLL_THUMBTRACK(MAP_WINDOW_VSCROLL, MapWindow::OnScroll) EVT_COMMAND_SCROLL_LINEUP(MAP_WINDOW_VSCROLL, MapWindow::OnScrollLineUp) EVT_COMMAND_SCROLL_LINEDOWN(MAP_WINDOW_VSCROLL, MapWindow::OnScrollLineDown) EVT_COMMAND_SCROLL_PAGEUP(MAP_WINDOW_VSCROLL, MapWindow::OnScrollPageUp) EVT_COMMAND_SCROLL_PAGEDOWN(MAP_WINDOW_VSCROLL, MapWindow::OnScrollPageDown) EVT_BUTTON(MAP_WINDOW_GEM, MapWindow::OnGem) END_EVENT_TABLE() BEGIN_EVENT_TABLE(MapScrollBar, wxScrollBar) EVT_KEY_DOWN(MapScrollBar::OnKey) EVT_KEY_UP(MapScrollBar::OnKey) EVT_CHAR(MapScrollBar::OnKey) EVT_SET_FOCUS(MapScrollBar::OnFocus) EVT_MOUSEWHEEL(MapScrollBar::OnWheel) END_EVENT_TABLE() #ifdef RME_CMAKE_BUILD wxIMPLEMENT_APP_NO_MAIN(Application); #else wxIMPLEMENT_APP(Application); #endif Application::~Application() { // Destroy } bool Application::OnInit() { #if defined __DEBUG_MODE__ && defined __WINDOWS__ _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); #endif spdlog::info("This is free software: you are free to change and redistribute it"); spdlog::info("There is NO WARRANTY, to the extent permitted by law"); spdlog::info("Review COPYING in RME distribution for details"); spdlog::info("Visit our website for updates, support, and resources: https://docs.opentibiabr.com/"); spdlog::info("Application started sucessfull!\n"); mt_seed(time(nullptr)); srand(time(nullptr)); // Discover data directory g_gui.discoverDataDirectory("clients.xml"); // Tell that we are the real thing wxAppConsole::SetInstance(this); wxArtProvider::Push(new ArtProvider()); // Load some internal stuff g_settings.load(); g_gui.LoadHotkeys(); ClientAssets::load(); #ifdef _USE_PROCESS_COM m_single_instance_checker = newd wxSingleInstanceChecker; // Instance checker has to stay alive throughout the applications lifetime if (g_settings.getInteger(Config::ONLY_ONE_INSTANCE) && m_single_instance_checker->IsAnotherRunning()) { RMEProcessClient client; wxConnectionBase* connection = client.MakeConnection("localhost", "rme_host", "rme_talk"); if (connection) { wxString fileName; if (ParseCommandLineMap(fileName)) { wxLogNull nolog; // We might get a timeout message if the file fails to open on the running instance. Let's not show that message. connection->Execute(fileName); } connection->Disconnect(); wxDELETE(connection); } wxDELETE(m_single_instance_checker); return false; // Since we return false - OnExit is never called } // We act as server then m_proc_server = newd RMEProcessServer(); if (!m_proc_server->Create("rme_host")) { wxLogWarning("Could not register IPC service!"); } #endif // Image handlers // wxImage::AddHandler(newd wxBMPHandler); wxImage::AddHandler(newd wxPNGHandler); wxImage::AddHandler(newd wxJPEGHandler); wxImage::AddHandler(newd wxTGAHandler); g_gui.gfx.loadEditorSprites(); #ifndef __DEBUG_MODE__ // wxHandleFatalExceptions(true); #endif m_file_to_open = wxEmptyString; ParseCommandLineMap(m_file_to_open); g_gui.root = newd MainFrame(__W_RME_APPLICATION_NAME__, wxDefaultPosition, wxSize(700, 500)); SetTopWindow(g_gui.root); g_gui.SetTitle(""); g_gui.root->LoadRecentFiles(); // Load palette g_gui.LoadPerspective(); // Initialize Lua scripting system if (!g_luaScripts.initialize()) { spdlog::error("Failed to initialize Lua scripting: {}", g_luaScripts.getLastError()); } else if (g_gui.root && g_gui.root->menu_bar) { g_gui.root->menu_bar->LoadScriptsMenu(); } wxIcon icon(rme_icon); g_gui.root->SetIcon(icon); if (g_settings.getInteger(Config::WELCOME_DIALOG) == 1 && m_file_to_open == wxEmptyString) { g_gui.ShowWelcomeDialog(icon); } else { g_gui.root->Show(); } // Set idle event handling mode wxIdleEvent::SetMode(wxIDLE_PROCESS_SPECIFIED); // Goto RME website? if (g_settings.getInteger(Config::GOTO_WEBSITE_ON_BOOT) == 1) { ::wxLaunchDefaultBrowser("http://www.remeresmapeditor.com/", wxBROWSER_NEW_WINDOW); g_settings.setInteger(Config::GOTO_WEBSITE_ON_BOOT, 0); } // Check for updates #ifdef _USE_UPDATER_ if (g_settings.getInteger(Config::USE_UPDATER) == -1) { int ret = g_gui.PopupDialog( "Notice", "Do you want the editor to automatically check for updates?\n" "It will connect to the internet if you choose yes.\n" "You can change this setting in the preferences later.", wxYES | wxNO ); if (ret == wxID_YES) { g_settings.setInteger(Config::USE_UPDATER, 1); } else { g_settings.setInteger(Config::USE_UPDATER, 0); } } if (g_settings.getInteger(Config::USE_UPDATER) == 1) { UpdateChecker updater; updater.connect(g_gui.root); } #endif FileName save_failed_file = GUI::GetLocalDataDirectory(); save_failed_file.SetName(".saving.txt"); if (save_failed_file.FileExists()) { std::ifstream f(nstr(save_failed_file.GetFullPath()).c_str(), std::ios::in); std::string backup_otbm, backup_house, backup_spawn, backup_spawn_npc; getline(f, backup_otbm); getline(f, backup_house); getline(f, backup_spawn); getline(f, backup_spawn_npc); // Remove the file f.close(); std::remove(nstr(save_failed_file.GetFullPath()).c_str()); // Query file retrieval if possible if (!backup_otbm.empty()) { long ret = g_gui.PopupDialog( "Editor Crashed", wxString( "IMPORTANT! THE EDITOR CRASHED WHILE SAVING!\n\n" "Do you want to recover the lost map? (it will be opened immediately):\n" ) << wxstr(backup_otbm) << "\n" << wxstr(backup_house) << "\n" << wxstr(backup_spawn) << "\n" << wxstr(backup_spawn_npc) << "\n", wxYES | wxNO ); if (ret == wxID_YES) { // Recover if the user so wishes std::remove(backup_otbm.substr(0, backup_otbm.size() - 1).c_str()); std::rename(backup_otbm.c_str(), backup_otbm.substr(0, backup_otbm.size() - 1).c_str()); if (!backup_house.empty()) { std::remove(backup_house.substr(0, backup_house.size() - 1).c_str()); std::rename(backup_house.c_str(), backup_house.substr(0, backup_house.size() - 1).c_str()); } if (!backup_spawn.empty()) { std::remove(backup_spawn.substr(0, backup_spawn.size() - 1).c_str()); std::rename(backup_spawn.c_str(), backup_spawn.substr(0, backup_spawn.size() - 1).c_str()); } if (!backup_spawn_npc.empty()) { std::remove(backup_spawn_npc.substr(0, backup_spawn_npc.size() - 1).c_str()); std::rename(backup_spawn_npc.c_str(), backup_spawn_npc.substr(0, backup_spawn_npc.size() - 1).c_str()); } // Load the map g_gui.LoadMap(wxstr(backup_otbm.substr(0, backup_otbm.size() - 1))); return true; } } } // Keep track of first event loop entry m_startup = true; return true; } void Application::OnEventLoopEnter(wxEventLoopBase* loop) { // First startup? if (!m_startup) { return; } m_startup = false; // Open a map. if (m_file_to_open != wxEmptyString) { g_gui.LoadMap(FileName(m_file_to_open)); } else if (!g_gui.IsWelcomeDialogShown() && g_gui.NewMap()) { // Open a new empty map // You generally don't want to save this map... g_gui.GetCurrentEditor()->clearChanges(); } } void Application::MacOpenFiles(const wxArrayString &fileNames) { if (!fileNames.IsEmpty()) { g_gui.LoadMap(FileName(fileNames.Item(0))); } } void Application::Unload() { g_gui.CloseAllEditors(); g_gui.SaveHotkeys(); g_gui.SavePerspective(); g_gui.root->SaveRecentFiles(); ClientAssets::save(); g_settings.save(true); g_gui.root = nullptr; } int Application::OnExit() { g_luaScripts.shutdown(); #ifdef _USE_PROCESS_COM wxDELETE(m_proc_server); wxDELETE(m_single_instance_checker); #endif return 1; } void Application::ShutdownServices() { g_luaScripts.shutdown(); #ifdef _USE_PROCESS_COM wxDELETE(m_proc_server); wxDELETE(m_single_instance_checker); #endif } void Application::OnFatalException() { //// } bool Application::ParseCommandLineMap(wxString &fileName) { if (argc == 2) { fileName = wxString(argv[1]); return true; } return false; } MainFrame::MainFrame(const wxString &title, const wxPoint &pos, const wxSize &size) : wxFrame((wxFrame*)nullptr, -1, title, pos, size, wxDEFAULT_FRAME_STYLE) { // Receive idle events SetExtraStyle(wxWS_EX_PROCESS_IDLE); #if wxCHECK_VERSION(3, 1, 0) // 3.1.0 or higher // Make sure ShowFullScreen() uses the full screen API on macOS EnableFullScreenView(true); #endif // Creates the file-dropdown menu menu_bar = newd MainMenuBar(this); wxArrayString warnings; wxString error; wxFileName filename; filename.Assign(g_gui.getFoundDataDirectory() + "menubar.xml"); if (!filename.FileExists()) { filename = FileName(GUI::GetDataDirectory() + "menubar.xml"); } if (!menu_bar->Load(filename, warnings, error)) { wxLogError(wxString() + "Could not load menubar.xml, editor will NOT be able to show its menu.\n"); } wxStatusBar* statusbar = CreateStatusBar(); statusbar->SetFieldsCount(4); SetStatusText(wxString("Welcome to ") << __W_RME_APPLICATION_NAME__ << " " << __W_RME_VERSION__); // Le sizer g_gui.aui_manager = newd wxAuiManager(this); g_gui.tabbook = newd MapTabbook(this, wxID_ANY); tool_bar = newd MainToolBar(this, g_gui.aui_manager); g_gui.aui_manager->AddPane(g_gui.tabbook, wxAuiPaneInfo().CenterPane().Floatable(false).CloseButton(false).PaneBorder(false)); g_gui.aui_manager->Update(); UpdateMenubar(); } MainFrame::~MainFrame() = default; void MainFrame::OnIdle(wxIdleEvent &event) { //// } #ifdef _USE_UPDATER_ void MainFrame::OnUpdateReceived(wxCommandEvent &event) { std::string data = *(std::string*)event.GetClientData(); delete (std::string*)event.GetClientData(); size_t first_colon = data.find(':'); size_t second_colon = data.find(':', first_colon + 1); if (first_colon == std::string::npos || second_colon == std::string::npos) { return; } std::string update = data.substr(0, first_colon); std::string verstr = data.substr(first_colon + 1, second_colon - first_colon - 1); std::string url = (second_colon == data.size() ? "" : data.substr(second_colon + 1)); if (update == "yes") { int ret = g_gui.PopupDialog( "Update Notice", wxString("There is a newd update available (") << wxstr(verstr) << "). Do you want to go to the website and download it?", wxYES | wxNO, "I don't want any update notices", Config::AUTOCHECK_FOR_UPDATES ); if (ret == wxID_YES) { ::wxLaunchDefaultBrowser(wxstr(url), wxBROWSER_NEW_WINDOW); } } } #endif void MainFrame::OnUpdateMenus(wxCommandEvent &) { UpdateMenubar(); g_gui.UpdateMinimap(true); g_gui.UpdateTitle(); } void MainFrame::OnUpdateActions(wxCommandEvent &) { tool_bar->UpdateButtons(); g_gui.RefreshActions(); } #ifdef __WINDOWS__ bool MainFrame::MSWTranslateMessage(WXMSG* msg) { if (g_gui.AreHotkeysEnabled()) { if (wxFrame::MSWTranslateMessage(msg)) { return true; } } else { if (wxWindow::MSWTranslateMessage(msg)) { return true; } } return false; } #endif void MainFrame::UpdateMenubar() { menu_bar->Update(); tool_bar->UpdateButtons(); } bool MainFrame::DoQueryClose() { Editor* editor = g_gui.GetCurrentEditor(); if (editor) { if (editor->IsLive()) { long ret = g_gui.PopupDialog( "Must Close Server", wxString("You are currently connected to a live server, to close this map the connection must be severed."), wxOK | wxCANCEL ); if (ret == wxID_OK) { editor->CloseLiveServer(); } else { return false; } } } return true; } bool MainFrame::DoQuerySaveTileset(bool doclose) { if (!g_materials.needSave()) { // skip dialog when there is nothing to save return true; } long ret = g_gui.PopupDialog( "Export tileset", "Do you want to export your tileset changes before exiting?", wxYES | wxNO | wxCANCEL ); if (ret == wxID_NO) { // "no" - exit without saving return true; } else if (ret == wxID_CANCEL) { // "cancel" - just close the dialog return false; } // "yes" button was pressed, open tileset exporting dialog if (g_gui.GetCurrentEditor()) { ExportTilesetsWindow dlg(this, *g_gui.GetCurrentEditor()); dlg.ShowModal(); } return !g_materials.needSave(); } bool MainFrame::DoQuerySave(bool doclose, bool checkTileset) { if (!g_gui.IsEditorOpen()) { return true; } if (checkTileset && !DoQuerySaveTileset()) { return false; } Editor &editor = *g_gui.GetCurrentEditor(); if (editor.IsLiveClient()) { long ret = g_gui.PopupDialog( "Disconnect", "Do you want to disconnect?", wxYES | wxNO ); if (ret != wxID_YES) { return false; } editor.CloseLiveServer(); return DoQuerySave(doclose); } else if (editor.IsLiveServer()) { long ret = g_gui.PopupDialog( "Shutdown", "Do you want to shut down the server? (any clients will be disconnected)", wxYES | wxNO ); if (ret != wxID_YES) { return false; } editor.CloseLiveServer(); return DoQuerySave(doclose); } else if (g_gui.ShouldSave()) { long ret = g_gui.PopupDialog( "Save changes", "Do you want to save your changes to \"" + wxstr(g_gui.GetCurrentMap().getName()) + "\"?", wxYES | wxNO | wxCANCEL ); if (ret == wxID_YES) { if (g_gui.GetCurrentMap().hasFile()) { g_gui.SaveCurrentMap(true); } else { wxFileDialog file(this, "Save...", "", "", "*.otbm", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); int32_t result = file.ShowModal(); if (result == wxID_OK) { g_gui.SaveCurrentMap(file.GetPath(), true); } else { return false; } } } else if (ret == wxID_CANCEL) { return false; } } if (doclose) { UnnamedRenderingLock(); g_gui.CloseCurrentEditor(); } return true; } void MainFrame::ShowMissingMonsters() { wxArrayString missingMonsters = g_monsters.getMissingMonsterNames(); if (!missingMonsters.IsEmpty()) { wxString missingMonstersStr = "Missing Monsters:\n" + wxJoin(missingMonsters, '\n'); g_gui.ShowTextBox(this, "Missing Monsters", missingMonstersStr); } } void MainFrame::ShowMissingNpcs() { wxArrayString missingNpcs = g_npcs.getMissingNpcNames(); if (!missingNpcs.IsEmpty()) { wxString missingNpcsStr = "Missing NPCs:\n" + wxJoin(missingNpcs, '\n'); g_gui.ShowTextBox(this, "Missing NPCs", missingNpcsStr); } } bool MainFrame::DoQueryImportCreatures() { if (!g_monsters.hasMissing() && !g_npcs.hasMissing()) { g_gui.RefreshPalettes(); return true; } std::string monstersLuaDir = g_settings.getString(Config::MONSTERS_LUA_DIRECTORY); std::string npcsLuaDir = g_settings.getString(Config::NPCS_LUA_DIRECTORY); const bool monstersLuaReady = !monstersLuaDir.empty() && wxDir::Exists(wxstr(monstersLuaDir)); const bool npcsLuaReady = !npcsLuaDir.empty() && wxDir::Exists(wxstr(npcsLuaDir)); bool needsConfig = (g_monsters.hasMissing() && !monstersLuaReady) || (g_npcs.hasMissing() && !npcsLuaReady); if (needsConfig) { long ret = g_gui.PopupDialog("Missing creatures", "There are missing creatures in the map. Would you like to configure the Lua directories to load them?", wxYES | wxNO); if (ret == wxID_YES) { PreferencesWindow dialog(g_gui.root); dialog.getBookCtrl().SetSelection(4); dialog.ShowModal(); monstersLuaDir = g_settings.getString(Config::MONSTERS_LUA_DIRECTORY); npcsLuaDir = g_settings.getString(Config::NPCS_LUA_DIRECTORY); } } if (g_monsters.hasMissing() && !monstersLuaDir.empty()) { wxString luaErr; wxArrayString luaWarn; if (!g_monsters.loadFromLuaDir(wxString(monstersLuaDir), luaErr, luaWarn)) { wxLogWarning("%s", luaErr); } for (const auto &warn : luaWarn) { wxLogWarning("%s", warn); } } if (g_npcs.hasMissing() && !npcsLuaDir.empty()) { wxString luaErr; wxArrayString luaWarn; if (!g_npcs.loadFromLuaDir(wxString(npcsLuaDir), luaErr, luaWarn)) { wxLogWarning("%s", luaErr); } for (const auto &warn : luaWarn) { wxLogWarning("%s", warn); } } ShowMissingMonsters(); ShowMissingNpcs(); g_gui.RefreshPalettes(); return true; } void MainFrame::UpdateFloorMenu() { menu_bar->UpdateFloorMenu(); } void MainFrame::UpdateIndicatorsMenu() { menu_bar->UpdateIndicatorsMenu(); } bool MainFrame::LoadMap(FileName name) { return g_gui.LoadMap(name); } void MainFrame::OnExit(wxCloseEvent &event) { if (!DoQuerySaveTileset()) { if (event.CanVeto()) { event.Veto(); return; } } std::set prompted; for (int i = 0; i < g_gui.tabbook->GetTabCount(); ++i) { auto* mapTab = dynamic_cast(g_gui.tabbook->GetTab(i)); if (!mapTab || !mapTab->GetMap() || !mapTab->GetMap()->hasChanged() || prompted.contains(mapTab->GetMap())) { continue; } prompted.insert(mapTab->GetMap()); g_gui.tabbook->SetFocusedTab(i); if (!DoQuerySave(false, false)) { if (event.CanVeto()) { event.Veto(); return; } break; } } g_gui.SaveHotkeys(); g_gui.SavePerspective(); g_gui.root->SaveRecentFiles(); ClientAssets::save(); g_settings.save(true); wxGetApp().ShutdownServices(); exit(0); } void MainFrame::AddRecentFile(const FileName &file) { menu_bar->AddRecentFile(file); } void MainFrame::LoadRecentFiles() { menu_bar->LoadRecentFiles(); } void MainFrame::SaveRecentFiles() { menu_bar->SaveRecentFiles(); } std::vector MainFrame::GetRecentFiles() { return menu_bar->GetRecentFiles(); } void MainFrame::PrepareDC(wxDC &dc) { dc.SetLogicalOrigin(0, 0); dc.SetAxisOrientation(1, 0); dc.SetUserScale(1.0, 1.0); dc.SetMapMode(wxMM_TEXT); } #ifdef RME_CMAKE_BUILD // CMake builds use the console subsystem, so provide main without letting // wxIMPLEMENT_APP generate a second wx entrypoint. int main(int argc, char** argv) { return wxEntry(argc, argv); } #endif