//////////////////////////////////////////////////////////////////////
// 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 "gui.h"
#include "main_menubar.h"
#include "editor.h"
#include "brush.h"
#include "map.h"
#include "sprites.h"
#include "materials.h"
#include "doodad_brush.h"
#include "spawn_monster_brush.h"
#include "common_windows.h"
#include "result_window.h"
#include "minimap_window.h"
#include "palette_window.h"
#include "map_display.h"
#include "application.h"
#include "welcome_dialog.h"
#include "spawn_npc_brush.h"
#include "actions_history_window.h"
#include "live_client.h"
#include "live_tab.h"
#include "live_server.h"
#ifdef __WXOSX__
#include
#endif
const wxEventType EVT_UPDATE_MENUS = wxNewEventType();
const wxEventType EVT_UPDATE_ACTIONS = wxNewEventType();
// Global GUI instance
GUI g_gui;
// GUI class implementation
GUI::GUI() :
aui_manager(nullptr),
root(nullptr),
minimap(nullptr),
gem(nullptr),
search_result_window(nullptr),
actions_history_window(nullptr),
secondary_map(nullptr),
doodad_buffer_map(nullptr),
house_brush(nullptr),
house_exit_brush(nullptr),
waypoint_brush(nullptr),
optional_brush(nullptr),
eraser(nullptr),
normal_door_brush(nullptr),
locked_door_brush(nullptr),
magic_door_brush(nullptr),
quest_door_brush(nullptr),
hatch_door_brush(nullptr),
window_door_brush(nullptr),
OGLContext(nullptr),
loaded_version(CLIENT_VERSION_NONE),
mode(SELECTION_MODE),
pasting(false),
hotkeys_enabled(true),
current_brush(nullptr),
previous_brush(nullptr),
brush_shape(BRUSHSHAPE_SQUARE),
brush_size(0),
brush_variation(0),
monster_spawntime(0),
npc_spawntime(0),
use_custom_thickness(false),
custom_thickness_mod(0.0),
progressBar(nullptr),
disabled_counter(0) {
doodad_buffer_map = newd BaseMap();
}
GUI::~GUI() {
delete doodad_buffer_map;
delete g_gui.aui_manager;
delete OGLContext;
}
wxGLContext* GUI::GetGLContext(wxGLCanvas* win) {
if (OGLContext == nullptr) {
#ifdef __WXOSX__
/*
wxGLContext(AGLPixelFormat fmt, wxGLCanvas *win,
const wxPalette& WXUNUSED(palette),
const wxGLContext *other
);
*/
OGLContext = new wxGLContext(win, nullptr);
#else
OGLContext = newd wxGLContext(win);
#endif
}
return OGLContext;
}
wxString GUI::GetDataDirectory() {
std::string cfg_str = g_settings.getString(Config::DATA_DIRECTORY);
if (!cfg_str.empty()) {
FileName dir;
dir.Assign(wxstr(cfg_str));
wxString path;
if (dir.DirExists()) {
path = dir.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR);
return path;
}
}
// Silently reset directory
FileName exec_directory;
try {
exec_directory = dynamic_cast(wxStandardPaths::Get()).GetExecutablePath();
} catch (const std::bad_cast &) {
throw; // Crash application (this should never happend anyways...)
}
exec_directory.AppendDir("data");
return exec_directory.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR);
}
wxString GUI::GetExecDirectory() {
// Silently reset directory
FileName exec_directory;
try {
exec_directory = dynamic_cast(wxStandardPaths::Get()).GetExecutablePath();
} catch (const std::bad_cast &) {
wxLogError("Could not fetch executable directory.");
}
return exec_directory.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR);
}
wxString GUI::GetLocalDataDirectory() {
if (g_settings.getInteger(Config::INDIRECTORY_INSTALLATION)) {
FileName dir = GetDataDirectory();
dir.AppendDir("user");
dir.AppendDir("data");
dir.Mkdir(0755, wxPATH_MKDIR_FULL);
return dir.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR);
;
} else {
FileName dir = dynamic_cast(wxStandardPaths::Get()).GetUserDataDir();
#ifdef __WINDOWS__
dir.AppendDir("Remere's Map Editor");
#else
dir.AppendDir(".rme");
#endif
dir.AppendDir("data");
dir.Mkdir(0755, wxPATH_MKDIR_FULL);
return dir.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR);
}
}
wxString GUI::GetLocalDirectory() {
if (g_settings.getInteger(Config::INDIRECTORY_INSTALLATION)) {
FileName dir = GetDataDirectory();
dir.AppendDir("user");
dir.Mkdir(0755, wxPATH_MKDIR_FULL);
return dir.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR);
;
} else {
FileName dir = dynamic_cast(wxStandardPaths::Get()).GetUserDataDir();
#ifdef __WINDOWS__
dir.AppendDir("Remere's Map Editor");
#else
dir.AppendDir(".rme");
#endif
dir.Mkdir(0755, wxPATH_MKDIR_FULL);
return dir.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR);
}
}
wxString GUI::GetExtensionsDirectory() {
std::string cfg_str = g_settings.getString(Config::EXTENSIONS_DIRECTORY);
if (!cfg_str.empty()) {
FileName dir;
dir.Assign(wxstr(cfg_str));
wxString path;
if (dir.DirExists()) {
path = dir.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR);
return path;
}
}
// Silently reset directory
FileName local_directory = GetLocalDirectory();
local_directory.AppendDir("extensions");
local_directory.Mkdir(0755, wxPATH_MKDIR_FULL);
return local_directory.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR);
}
void GUI::discoverDataDirectory(const wxString &existentFile) {
wxString currentDir = wxGetCwd();
wxString execDir = GetExecDirectory();
wxString possiblePaths[] = {
execDir,
currentDir + "/",
// these are used usually when running from build directories
execDir + "/../",
execDir + "/../../",
execDir + "/../../../",
currentDir + "/../",
};
bool found = false;
for (const wxString &path : possiblePaths) {
if (wxFileName(path + "data/" + existentFile).FileExists()) {
m_dataDirectory = path + "data/";
found = true;
break;
}
}
if (!found) {
wxLogError(wxString() + "Could not find data directory.\n");
}
}
bool GUI::LoadVersion(ClientVersionID version, wxString &error, wxArrayString &warnings, bool force) {
if (ClientVersion::get(version) == nullptr) {
error = "Unsupported client version! (8)";
return false;
}
if (version != loaded_version || force) {
if (getLoadedVersion() != nullptr) {
// There is another version loaded right now, save window layout
g_gui.SavePerspective();
}
// Disable all rendering so the data is not accessed while reloading
UnnamedRenderingLock();
DestroyPalettes();
DestroyMinimap();
// Destroy the previous version
UnloadVersion();
loaded_version = version;
if (!getLoadedVersion()->hasValidPaths()) {
if (!getLoadedVersion()->loadValidPaths()) {
error = "Couldn't load relevant asset files";
loaded_version = CLIENT_VERSION_NONE;
return false;
}
}
bool ret = LoadDataFiles(error, warnings);
if (ret) {
g_gui.LoadPerspective();
} else {
loaded_version = CLIENT_VERSION_NONE;
}
return ret;
}
return true;
}
void GUI::EnableHotkeys() {
hotkeys_enabled = true;
}
void GUI::DisableHotkeys() {
hotkeys_enabled = false;
}
bool GUI::AreHotkeysEnabled() const {
return hotkeys_enabled;
}
ClientVersionID GUI::GetCurrentVersionID() const {
if (loaded_version != CLIENT_VERSION_NONE) {
return getLoadedVersion()->getID();
}
return CLIENT_VERSION_NONE;
}
const ClientVersion &GUI::GetCurrentVersion() const {
assert(loaded_version);
return *getLoadedVersion();
}
void GUI::CycleTab(bool forward) {
tabbook->CycleTab(forward);
}
bool GUI::LoadDataFiles(wxString &error, wxArrayString &warnings) {
FileName data_path = getLoadedVersion()->getDataPath();
FileName client_path = getLoadedVersion()->getClientPath();
FileName extension_path = GetExtensionsDirectory();
FileName exec_directory;
try {
exec_directory = dynamic_cast(wxStandardPaths::Get()).GetExecutablePath();
} catch (std::bad_cast &) {
error = "Couldn't establish working directory...";
return false;
}
g_gui.gfx.client_version = getLoadedVersion();
if (!g_gui.gfx.loadOTFI(client_path.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR), error, warnings)) {
error = "Couldn't load otfi file: " + error;
g_gui.DestroyLoadBar();
UnloadVersion();
return false;
}
g_gui.CreateLoadBar("Loading asset files");
g_gui.SetLoadDone(0, "Loading metadata file...");
wxFileName metadata_path = g_gui.gfx.getMetadataFileName();
if (!g_gui.gfx.loadSpriteMetadata(metadata_path, error, warnings)) {
error = "Couldn't load metadata: " + error;
g_gui.DestroyLoadBar();
UnloadVersion();
return false;
}
g_gui.SetLoadDone(10, "Loading sprites file...");
wxFileName sprites_path = g_gui.gfx.getSpritesFileName();
if (!g_gui.gfx.loadSpriteData(sprites_path.GetFullPath(), error, warnings)) {
error = "Couldn't load sprites: " + error;
g_gui.DestroyLoadBar();
UnloadVersion();
return false;
}
g_gui.SetLoadDone(20, "Loading items.otb file...");
if (!g_items.loadFromOtb(wxString("data/items/items.otb"), error, warnings)) {
error = "Couldn't load items.otb: " + error;
g_gui.DestroyLoadBar();
UnloadVersion();
return false;
}
g_gui.SetLoadDone(30, "Loading items.xml ...");
if (!g_items.loadFromGameXml(wxString("data/items/items.xml"), error, warnings)) {
warnings.push_back("Couldn't load items.xml: " + error);
}
g_gui.SetLoadDone(45, "Loading monsters.xml ...");
if (!g_monsters.loadFromXML(wxString("data/creatures/monsters.xml"), true, error, warnings)) {
warnings.push_back("Couldn't load monsters.xml: " + error);
}
g_gui.SetLoadDone(45, "Loading user monsters.xml ...");
{
FileName cdb = getLoadedVersion()->getLocalDataPath();
cdb.SetFullName("monsters.xml");
wxString nerr;
wxArrayString nwarn;
g_monsters.loadFromXML(cdb, false, nerr, nwarn);
}
g_gui.SetLoadDone(45, "Loading npcs.xml ...");
if (!g_npcs.loadFromXML(wxString("data/creatures/npcs.xml"), true, error, warnings)) {
warnings.push_back("Couldn't load npcs.xml: " + error);
}
g_gui.SetLoadDone(45, "Loading user npcs.xml ...");
{
FileName cdb = getLoadedVersion()->getLocalDataPath();
cdb.SetFullName("npcs.xml");
wxString nerr;
wxArrayString nwarn;
g_npcs.loadFromXML(cdb, false, nerr, nwarn);
}
g_gui.SetLoadDone(50, "Loading materials.xml ...");
if (!g_materials.loadMaterials(wxString(data_path.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR) + "materials.xml"), error, warnings)) {
warnings.push_back("Couldn't load materials.xml: " + error);
}
g_gui.SetLoadDone(70, "Loading extensions...");
if (!g_materials.loadExtensions(extension_path, error, warnings)) {
// warnings.push_back("Couldn't load extensions: " + error);
}
g_gui.SetLoadDone(70, "Finishing...");
g_brushes.init();
g_materials.createOtherTileset();
g_materials.createNpcTileset();
g_gui.DestroyLoadBar();
return true;
}
void GUI::UnloadVersion() {
UnnamedRenderingLock();
gfx.clear();
current_brush = nullptr;
previous_brush = nullptr;
house_brush = nullptr;
house_exit_brush = nullptr;
waypoint_brush = nullptr;
optional_brush = nullptr;
eraser = nullptr;
normal_door_brush = nullptr;
locked_door_brush = nullptr;
magic_door_brush = nullptr;
quest_door_brush = nullptr;
hatch_door_brush = nullptr;
window_door_brush = nullptr;
if (loaded_version != CLIENT_VERSION_NONE) {
// g_gui.UnloadVersion();
g_materials.clear();
g_brushes.clear();
g_items.clear();
gfx.clear();
FileName cdb = getLoadedVersion()->getLocalDataPath();
cdb.SetFullName("monsters.xml");
g_monsters.saveToXML(cdb);
g_monsters.clear();
cdb.SetFullName("npcs.xml");
g_npcs.saveToXML(cdb);
g_npcs.clear();
loaded_version = CLIENT_VERSION_NONE;
}
}
void GUI::SaveCurrentMap(FileName filename, bool showdialog) {
MapTab* mapTab = GetCurrentMapTab();
if (mapTab) {
Editor* editor = mapTab->GetEditor();
if (editor) {
editor->saveMap(filename, showdialog);
const std::string &filename = editor->getMap().getFilename();
const Position &position = mapTab->GetScreenCenterPosition();
std::ostringstream stream;
stream << position;
g_settings.setString(Config::RECENT_EDITED_MAP_PATH, filename);
g_settings.setString(Config::RECENT_EDITED_MAP_POSITION, stream.str());
}
}
UpdateTitle();
root->UpdateMenubar();
root->Refresh();
}
bool GUI::IsEditorOpen() const {
return tabbook != nullptr && GetCurrentMapTab();
}
double GUI::GetCurrentZoom() {
MapTab* tab = GetCurrentMapTab();
if (tab) {
return tab->GetCanvas()->GetZoom();
}
return 1.0;
}
void GUI::SetCurrentZoom(double zoom) {
MapTab* tab = GetCurrentMapTab();
if (tab) {
tab->GetCanvas()->SetZoom(zoom);
}
}
void GUI::FitViewToMap() {
for (int index = 0; index < tabbook->GetTabCount(); ++index) {
if (auto* tab = dynamic_cast(tabbook->GetTab(index))) {
tab->GetView()->FitToMap();
}
}
}
void GUI::FitViewToMap(MapTab* mt) {
for (int index = 0; index < tabbook->GetTabCount(); ++index) {
if (auto* tab = dynamic_cast(tabbook->GetTab(index))) {
if (tab->HasSameReference(mt)) {
tab->GetView()->FitToMap();
}
}
}
}
bool GUI::NewMap() {
FinishWelcomeDialog();
Editor* editor;
try {
editor = newd Editor(copybuffer);
} catch (std::runtime_error &e) {
PopupDialog(root, "Error!", wxString(e.what(), wxConvUTF8), wxOK);
return false;
}
auto* mapTab = newd MapTab(tabbook, editor);
mapTab->OnSwitchEditorMode(mode);
editor->clearChanges();
SetStatusText("Created new map");
UpdateTitle();
RefreshPalettes();
root->UpdateMenubar();
root->Refresh();
return true;
}
void GUI::OpenMap() {
wxString wildcard = g_settings.getInteger(Config::USE_OTGZ) != 0 ? MAP_LOAD_FILE_WILDCARD_OTGZ : MAP_LOAD_FILE_WILDCARD;
wxFileDialog dialog(root, "Open map file", wxEmptyString, wxEmptyString, wildcard, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dialog.ShowModal() == wxID_OK) {
LoadMap(dialog.GetPath());
}
}
void GUI::SaveMap() {
if (!IsEditorOpen()) {
return;
}
if (GetCurrentMap().hasFile()) {
SaveCurrentMap(true);
} else {
wxString wildcard = g_settings.getInteger(Config::USE_OTGZ) != 0 ? MAP_SAVE_FILE_WILDCARD_OTGZ : MAP_SAVE_FILE_WILDCARD;
wxFileDialog dialog(root, "Save...", wxEmptyString, wxEmptyString, wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if (dialog.ShowModal() == wxID_OK) {
SaveCurrentMap(dialog.GetPath(), true);
}
}
}
void GUI::SaveMapAs() {
if (!IsEditorOpen()) {
return;
}
wxString wildcard = g_settings.getInteger(Config::USE_OTGZ) != 0 ? MAP_SAVE_FILE_WILDCARD_OTGZ : MAP_SAVE_FILE_WILDCARD;
wxFileDialog dialog(root, "Save As...", "", "", wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if (dialog.ShowModal() == wxID_OK) {
SaveCurrentMap(dialog.GetPath(), true);
UpdateTitle();
root->menu_bar->AddRecentFile(dialog.GetPath());
root->UpdateMenubar();
}
}
bool GUI::LoadMap(const FileName &fileName) {
FinishWelcomeDialog();
if (GetCurrentEditor() && !GetCurrentMap().hasChanged() && !GetCurrentMap().hasFile()) {
g_gui.CloseCurrentEditor();
}
Editor* editor;
try {
editor = newd Editor(copybuffer, fileName);
} catch (std::runtime_error &e) {
PopupDialog(root, "Error!", wxString(e.what(), wxConvUTF8), wxOK);
return false;
}
auto* mapTab = newd MapTab(tabbook, editor);
mapTab->OnSwitchEditorMode(mode);
root->AddRecentFile(fileName);
mapTab->GetView()->FitToMap();
UpdateTitle();
ListDialog("Map loader errors", mapTab->GetMap()->getWarnings());
// Npc and monsters
root->DoQueryImportCreatures();
FitViewToMap(mapTab);
root->UpdateMenubar();
std::string path = g_settings.getString(Config::RECENT_EDITED_MAP_PATH);
if (!path.empty()) {
FileName file(path);
if (file == fileName) {
std::istringstream stream(g_settings.getString(Config::RECENT_EDITED_MAP_POSITION));
Position position;
stream >> position;
mapTab->SetScreenCenterPosition(position);
}
}
return true;
}
Editor* GUI::GetCurrentEditor() {
MapTab* mapTab = GetCurrentMapTab();
if (mapTab) {
return mapTab->GetEditor();
}
return nullptr;
}
EditorTab* GUI::GetTab(int idx) {
return tabbook->GetTab(idx);
}
int GUI::GetTabCount() const {
return tabbook->GetTabCount();
}
EditorTab* GUI::GetCurrentTab() {
return tabbook->GetCurrentTab();
}
MapTab* GUI::GetCurrentMapTab() const {
if (tabbook && tabbook->GetTabCount() > 0) {
EditorTab* editorTab = tabbook->GetCurrentTab();
auto* mapTab = dynamic_cast(editorTab);
return mapTab;
}
return nullptr;
}
Map &GUI::GetCurrentMap() {
Editor* editor = GetCurrentEditor();
ASSERT(editor);
return editor->getMap();
}
int GUI::GetOpenMapCount() {
std::set