//////////////////////////////////////////////////////////////////////
// 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::~GUI() {
delete doodadBufferMap;
delete g_gui.auiManager;
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() {
const auto &configDataDirectory = g_settings.getString(Config::DATA_DIRECTORY);
if (!configDataDirectory.empty()) {
FileName dir;
dir.Assign(wxstr(configDataDirectory));
if (dir.DirExists()) {
return dir.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR);
}
}
// Silently reset directory
FileName executableDirectory = GetExecFileName();
executableDirectory.AppendDir("data");
return executableDirectory.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR);
}
FileName GUI::GetExecFileName() {
// Silently reset directory
FileName executableDirectory;
try {
executableDirectory = dynamic_cast(wxStandardPaths::Get()).GetExecutablePath();
} catch (const std::bad_cast &) {
wxLogError("Could not fetch executable directory.");
}
return executableDirectory;
}
wxString GUI::GetExecDirectory() {
return GetExecFileName().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() {
const auto &configDataDirectory = g_settings.getString(Config::EXTENSIONS_DIRECTORY);
if (!configDataDirectory.empty()) {
FileName dir;
dir.Assign(wxstr(configDataDirectory));
if (dir.DirExists()) {
return dir.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR);
}
}
// Silently reset directory
FileName localDirectory = GetLocalDirectory();
localDirectory.AppendDir("extensions");
localDirectory.Mkdir(0755, wxPATH_MKDIR_FULL);
return localDirectory.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR);
}
void GUI::discoverDataDirectory(const wxString &existentFile) {
wxString currentDirectory = wxGetCwd();
wxString execDirectory = GetExecDirectory();
wxString possiblePaths[] = {
execDirectory,
currentDirectory + "/",
// these are used usually when running from build directories
execDirectory + "/../",
execDirectory + "/../../",
execDirectory + "/../../../",
currentDirectory + "/../",
};
auto found = false;
for (const auto &path : possiblePaths) {
if (wxFileName(wxString::Format("%sdata/%s", path, existentFile)).FileExists()) {
m_dataDirectory = wxString::Format("%sdata/", path);
found = true;
break;
}
}
if (!found) {
wxLogError(wxString::Format("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 != loadedVersion || 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();
loadedVersion = version;
if (!getLoadedVersion()->hasValidPaths()) {
if (!getLoadedVersion()->loadValidPaths()) {
error = "Couldn't load relevant asset files";
loadedVersion = CLIENT_VERSION_NONE;
return false;
}
}
const auto ret = LoadDataFiles(error, warnings);
if (ret) {
g_gui.LoadPerspective();
} else {
loadedVersion = CLIENT_VERSION_NONE;
}
return ret;
}
return true;
}
ClientVersionID GUI::GetCurrentVersionID() const {
if (loadedVersion != CLIENT_VERSION_NONE) {
return getLoadedVersion()->getID();
}
return CLIENT_VERSION_NONE;
}
const ClientVersion &GUI::GetCurrentVersion() const {
assert(loadedVersion);
return *getLoadedVersion();
}
void GUI::CycleTab(bool forward) {
tabbook->CycleTab(forward);
}
bool GUI::LoadDataFiles(wxString &error, wxArrayString &warnings) {
const auto &dataPath = getLoadedVersion()->getDataPath();
const auto &clientPath = getLoadedVersion()->getClientPath();
const auto &extensionPath = GetExtensionsDirectory();
FileName executableDirectory;
try {
executableDirectory = 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(clientPath.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR), error, warnings)) {
error = wxString::Format("Couldn't load otfi file: %s", error);
g_gui.DestroyLoadBar();
UnloadVersion();
return false;
}
g_gui.CreateLoadBar("Loading asset files");
g_gui.SetLoadDone(0, "Loading metadata file...");
const auto &metadataPath = g_gui.gfx.getMetadataFileName();
if (!g_gui.gfx.loadSpriteMetadata(metadataPath, error, warnings)) {
error = wxString::Format("Couldn't load metadata: %s", error);
g_gui.DestroyLoadBar();
UnloadVersion();
return false;
}
g_gui.SetLoadDone(10, "Loading sprites file...");
const auto &spritesPath = g_gui.gfx.getSpritesFileName();
if (!g_gui.gfx.loadSpriteData(spritesPath.GetFullPath(), error, warnings)) {
error = wxString::Format("Couldn't load sprites: %s", 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 = wxString::Format("Couldn't load items.otb: %s", 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(wxString::Format("Couldn't load items.xml: %s", error));
}
g_gui.SetLoadDone(45, "Loading monsters.xml ...");
if (!g_monsters.loadFromXML(wxString("data/creatures/monsters.xml"), true, error, warnings)) {
warnings.push_back(wxString::Format("Couldn't load monsters.xml: %s", error));
}
g_gui.SetLoadDone(45, "Loading user monsters.xml ...");
{
FileName monstersDataPath = getLoadedVersion()->getLocalDataPath();
monstersDataPath.SetFullName("monsters.xml");
wxString monstersError;
wxArrayString monstersWarning;
g_monsters.loadFromXML(monstersDataPath, false, monstersError, monstersWarning);
}
g_gui.SetLoadDone(45, "Loading npcs.xml ...");
if (!g_npcs.loadFromXML(wxString("data/creatures/npcs.xml"), true, error, warnings)) {
warnings.push_back(wxString::Format("Couldn't load npcs.xml: %s", error));
}
g_gui.SetLoadDone(45, "Loading user npcs.xml ...");
{
FileName npcsDataPath = getLoadedVersion()->getLocalDataPath();
npcsDataPath.SetFullName("npcs.xml");
wxString npcsError;
wxArrayString npcWarnings;
g_npcs.loadFromXML(npcsDataPath, false, npcsError, npcWarnings);
}
g_gui.SetLoadDone(50, "Loading materials.xml ...");
if (!g_materials.loadMaterials(wxString::Format("%smaterials.xml", dataPath.GetPath(wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR)), error, warnings)) {
warnings.push_back(wxString::Format("Couldn't load materials.xml: %s", error));
}
g_gui.SetLoadDone(70, "Loading extensions...");
if (!g_materials.loadExtensions(extensionPath, error, warnings)) {
// warnings.push_back("Couldn't load extensions: " + error);
}
g_gui.SetLoadDone(90, "Finishing...");
g_brushes.init();
g_materials.createOtherTileset();
g_materials.createNpcTileset();
g_gui.DestroyLoadBar();
return true;
}
void GUI::UnloadVersion() {
UnnamedRenderingLock();
gfx.clear();
currentBrush = nullptr;
previousBrush = nullptr;
houseBrush = nullptr;
houseExitBrush = nullptr;
waypointBrush = nullptr;
optionalBrush = nullptr;
eraser = nullptr;
normalDoorBrush = nullptr;
lockedDoorBrush = nullptr;
magicDoorBrush = nullptr;
questDoorBrush = nullptr;
hatchDoorBrush = nullptr;
windowDoorBrush = nullptr;
if (loadedVersion != CLIENT_VERSION_NONE) {
// g_gui.UnloadVersion();
g_materials.clear();
g_brushes.clear();
g_items.clear();
gfx.clear();
FileName localDataPath = getLoadedVersion()->getLocalDataPath();
localDataPath.SetFullName("monsters.xml");
g_monsters.saveToXML(localDataPath);
g_monsters.clear();
localDataPath.SetFullName("npcs.xml");
g_npcs.saveToXML(localDataPath);
g_npcs.clear();
loadedVersion = CLIENT_VERSION_NONE;
}
}
void GUI::SaveCurrentMap(FileName filename, bool showdialog) {
const auto &mapTab = GetCurrentMapTab();
if (mapTab) {
const auto &editor = mapTab->GetEditor();
if (editor) {
editor->saveMap(filename, showdialog);
const auto &mapName = editor->getMap().getFilename();
const auto &position = mapTab->GetScreenCenterPosition();
g_settings.setString(Config::RECENT_EDITED_MAP_PATH, mapName);
g_settings.setString(Config::RECENT_EDITED_MAP_POSITION, position.toString());
}
}
UpdateTitle();
root->UpdateMenubar();
root->Refresh();
}
bool GUI::IsEditorOpen() const {
return tabbook != nullptr && GetCurrentMapTab();
}
double GUI::GetCurrentZoom() {
const auto &tab = GetCurrentMapTab();
if (tab) {
return tab->GetCanvas()->GetZoom();
}
return 1.0;
}
void GUI::SetCurrentZoom(double zoom) {
const auto &tab = GetCurrentMapTab();
if (tab) {
tab->GetCanvas()->SetZoom(zoom);
}
}
void GUI::FitViewToMap() {
for (int index = 0; index < tabbook->GetTabCount(); ++index) {
if (const auto &tab = dynamic_cast(tabbook->GetTab(index))) {
tab->GetView()->FitToMap();
}
}
}
void GUI::FitViewToMap(MapTab* mt) {
for (int index = 0; index < tabbook->GetTabCount(); ++index) {
if (const 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 (const std::runtime_error &e) {
PopupDialog(root, "Error!", wxString(e.what(), wxConvUTF8), wxOK);
return false;
}
const 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() {
const auto &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 {
const auto &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;
}
const auto &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 (const std::runtime_error &e) {
PopupDialog(root, "Error!", wxString(e.what(), wxConvUTF8), wxOK);
return false;
}
const 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) {
Position position = Position(g_settings.getString(Config::RECENT_EDITED_MAP_POSITION));
mapTab->SetScreenCenterPosition(position);
}
}
return true;
}
Editor* GUI::GetCurrentEditor() {
const auto &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) {
const auto &editorTab = tabbook->GetCurrentTab();
const auto &mapTab = dynamic_cast(editorTab);
return mapTab;
}
return nullptr;
}
Map &GUI::GetCurrentMap() {
const auto &editor = GetCurrentEditor();
ASSERT(editor);
return editor->getMap();
}
int GUI::GetOpenMapCount() {
std::set