diff --git a/Mods/vcmi/Data/lobby/iconFolder.png b/Mods/vcmi/Data/lobby/iconFolder.png new file mode 100644 index 000000000..44547ef38 Binary files /dev/null and b/Mods/vcmi/Data/lobby/iconFolder.png differ diff --git a/client/lobby/CSavingScreen.cpp b/client/lobby/CSavingScreen.cpp index 562643a92..1126fb444 100644 --- a/client/lobby/CSavingScreen.cpp +++ b/client/lobby/CSavingScreen.cpp @@ -70,7 +70,7 @@ void CSavingScreen::saveGame() if(!(tabSel && tabSel->inputName && tabSel->inputName->getText().size())) return; - std::string path = "Saves/" + tabSel->inputName->getText(); + std::string path = "Saves/" + tabSel->curFolder + tabSel->inputName->getText(); auto overWrite = [this, path]() -> void { diff --git a/client/lobby/SelectionTab.cpp b/client/lobby/SelectionTab.cpp index ed3d82de6..592f70048 100644 --- a/client/lobby/SelectionTab.cpp +++ b/client/lobby/SelectionTab.cpp @@ -27,6 +27,7 @@ #include "../windows/GUIClasses.h" #include "../windows/InfoWindows.h" #include "../render/CAnimation.h" +#include "../render/IImage.h" #include "../../CCallback.h" @@ -41,8 +42,16 @@ #include "../../lib/mapping/MapFormat.h" #include "../../lib/serializer/Connection.h" -bool mapSorter::operator()(const std::shared_ptr aaa, const std::shared_ptr bbb) +bool mapSorter::operator()(const std::shared_ptr aaa, const std::shared_ptr bbb) { + if(aaa->isFolder || bbb->isFolder) + { + if(aaa->isFolder != bbb->isFolder) + return (aaa->isFolder > bbb->isFolder); + else + return boost::ilexicographical_compare(aaa->folderName, bbb->folderName); + } + auto a = aaa->mapHeader.get(); auto b = bbb->mapHeader.get(); if(a && b) //if we are sorting scenarios @@ -130,7 +139,7 @@ static ESortBy getSortBySelectionScreen(ESelectionScreen Type) } SelectionTab::SelectionTab(ESelectionScreen Type) - : CIntObject(LCLICK | SHOW_POPUP | KEYBOARD | DOUBLECLICK), callOnSelect(nullptr), tabType(Type), selectionPos(0), sortModeAscending(true), inputNameRect{32, 539, 350, 20} + : CIntObject(LCLICK | SHOW_POPUP | KEYBOARD | DOUBLECLICK), callOnSelect(nullptr), tabType(Type), selectionPos(0), sortModeAscending(true), inputNameRect{32, 539, 350, 20}, curFolder(""), currentMapSizeFilter(0) { OBJ_CONSTRUCTION; @@ -234,6 +243,7 @@ void SelectionTab::toggleMode() case ESelectionScreen::saveGame: parseSaves(getFiles("Saves/", EResType::SAVEGAME)); inputName->enable(); + inputName->activate(); restoreLastSelection(); break; @@ -313,6 +323,15 @@ void SelectionTab::keyPressed(EShortcut key) void SelectionTab::clickDouble(const Point & cursorPosition) { + int position = getLine(); + int itemIndex = position + slider->getValue(); + + if(itemIndex >= curItems.size()) + return; + + if(itemIndex >= 0 && curItems[itemIndex]->isFolder) + return; + if(getLine() != -1) //double clicked scenarios list { (static_cast(parent))->buttonStart->clickPressed(cursorPosition); @@ -328,29 +347,97 @@ void SelectionTab::showPopupWindow(const Point & cursorPosition) if(py >= curItems.size()) return; - std::string text = boost::str(boost::format("{%1%}\r\n\r\n%2%:\r\n%3%") % curItems[py]->getName() % CGI->generaltexth->translate("vcmi.lobby.filename") % curItems[py]->fileURI); - if(curItems[py]->date != "") - text += boost::str(boost::format("\r\n\r\n%1%:\r\n%2%") % CGI->generaltexth->translate("vcmi.lobby.creationDate") % curItems[py]->date); + if(!curItems[py]->isFolder) + { + std::string text = boost::str(boost::format("{%1%}\r\n\r\n%2%:\r\n%3%") % curItems[py]->getName() % CGI->generaltexth->translate("vcmi.lobby.filename") % curItems[py]->fullFileURI); + if(curItems[py]->date != "") + text += boost::str(boost::format("\r\n\r\n%1%:\r\n%2%") % CGI->generaltexth->translate("vcmi.lobby.creationDate") % curItems[py]->date); - CRClickPopup::createAndPush(text); + CRClickPopup::createAndPush(text); + } +} + +auto SelectionTab::checkSubfolder(std::string path) +{ + struct Ret + { + std::string folderName; + std::string baseFolder; + bool parentExists; + bool fileInFolder; + } ret; + + ret.parentExists = (curFolder != ""); + ret.fileInFolder = false; + + std::vector filetree; + // delete first element (e.g. 'MAPS') + boost::split(filetree, path, boost::is_any_of("/")); + filetree.erase(filetree.begin()); + std::string pathWithoutPrefix = boost::algorithm::join(filetree, "/"); + + if(!filetree.empty()) + { + filetree.pop_back(); + ret.baseFolder = boost::algorithm::join(filetree, "/"); + } + else + ret.baseFolder = ""; + + if(boost::algorithm::starts_with(ret.baseFolder, curFolder)) + { + std::string folder = ret.baseFolder.substr(curFolder.size()); + + if(folder != "") + { + boost::split(filetree, folder, boost::is_any_of("/")); + ret.folderName = filetree[0]; + } + } + + if(boost::algorithm::starts_with(pathWithoutPrefix, curFolder)) + if(boost::count(pathWithoutPrefix.substr(curFolder.size()), '/') == 0) + ret.fileInFolder = true; + + return ret; } // A new size filter (Small, Medium, ...) has been selected. Populate // selMaps with the relevant data. void SelectionTab::filter(int size, bool selectFirst) { + if(size == -1) + size = currentMapSizeFilter; + currentMapSizeFilter = size; + curItems.clear(); - if(tabType == ESelectionScreen::campaignList) + for(auto elem : allItems) { - for(auto elem : allItems) - curItems.push_back(elem); - } - else - { - for(auto elem : allItems) + if((elem->mapHeader && (!size || elem->mapHeader->width == size)) || tabType == ESelectionScreen::campaignList) { - if(elem->mapHeader && (!size || elem->mapHeader->width == size)) + auto [folderName, baseFolder, parentExists, fileInFolder] = checkSubfolder(elem->originalFileURI); + + if(parentExists) + { + auto folder = std::make_shared(); + folder->isFolder = true; + folder->folderName = ".. (" + curFolder + ")"; + auto itemIt = boost::range::find_if(curItems, [](std::shared_ptr e) { return boost::starts_with(e->folderName, ".."); }); + if (itemIt == curItems.end()) { + curItems.push_back(folder); + } + } + + std::shared_ptr folder = std::make_shared(); + folder->isFolder = true; + folder->folderName = folderName; + auto itemIt = boost::range::find_if(curItems, [folder](std::shared_ptr e) { return e->folderName == folder->folderName; }); + if (itemIt == curItems.end() && folderName != "") { + curItems.push_back(folder); + } + + if(fileInFolder) curItems.push_back(elem); } } @@ -362,13 +449,19 @@ void SelectionTab::filter(int size, bool selectFirst) sort(); if(selectFirst) { - slider->scrollTo(0); - callOnSelect(curItems[0]); - selectAbs(0); + int firstPos = boost::range::find_if(curItems, [](std::shared_ptr e) { return !e->isFolder; }) - curItems.begin(); + if(firstPos < curItems.size()) + { + slider->scrollTo(firstPos); + callOnSelect(curItems[firstPos]); + selectAbs(firstPos); + } } } else { + updateListItems(); + redraw(); slider->block(true); if(callOnSelect) callOnSelect(nullptr); @@ -388,7 +481,7 @@ void SelectionTab::sortBy(int criteria) } sort(); - selectAbs(0); + selectAbs(-1); } void SelectionTab::sort() @@ -397,8 +490,9 @@ void SelectionTab::sort() std::stable_sort(curItems.begin(), curItems.end(), mapSorter(generalSortingBy)); std::stable_sort(curItems.begin(), curItems.end(), mapSorter(sortingBy)); + int firstMapIndex = boost::range::find_if(curItems, [](std::shared_ptr e) { return !e->isFolder; }) - curItems.begin(); if(!sortModeAscending) - std::reverse(curItems.begin(), curItems.end()); + std::reverse(std::next(curItems.begin(), firstMapIndex), curItems.end()); updateListItems(); redraw(); @@ -421,6 +515,29 @@ void SelectionTab::select(int position) else if(position >= listItems.size()) slider->scrollBy(position - (int)listItems.size() + 1); + if(curItems[py]->isFolder) { + if(boost::starts_with(curItems[py]->folderName, "..")) + { + std::vector filetree; + boost::split(filetree, curFolder, boost::is_any_of("/")); + filetree.pop_back(); + filetree.pop_back(); + curFolder = filetree.size() > 0 ? boost::algorithm::join(filetree, "/") + "/" : ""; + } + else + curFolder += curItems[py]->folderName + "/"; + filter(-1); + slider->scrollTo(0); + + int firstPos = boost::range::find_if(curItems, [](std::shared_ptr e) { return !e->isFolder; }) - curItems.begin(); + if(firstPos < curItems.size()) + { + selectAbs(firstPos); + } + + return; + } + rememberCurrentSelection(); if(inputName && inputName->isActive()) @@ -437,6 +554,8 @@ void SelectionTab::select(int position) void SelectionTab::selectAbs(int position) { + if(position == -1) + position = boost::range::find_if(curItems, [](std::shared_ptr e) { return !e->isFolder; }) - curItems.begin(); select(position - slider->getValue()); } @@ -502,6 +621,16 @@ int SelectionTab::getLine(const Point & clickPos) const void SelectionTab::selectFileName(std::string fname) { boost::to_upper(fname); + + for(int i = (int)allItems.size() - 1; i >= 0; i--) + { + if(allItems[i]->fileURI == fname) + { + auto [folderName, baseFolder, parentExists, fileInFolder] = checkSubfolder(allItems[i]->originalFileURI); + curFolder = baseFolder != "" ? baseFolder + "/" : ""; + } + } + for(int i = (int)curItems.size() - 1; i >= 0; i--) { if(curItems[i]->fileURI == fname) @@ -512,16 +641,20 @@ void SelectionTab::selectFileName(std::string fname) } } - selectAbs(0); + filter(-1); + selectAbs(-1); } -std::shared_ptr SelectionTab::getSelectedMapInfo() const +std::shared_ptr SelectionTab::getSelectedMapInfo() const { - return curItems.empty() ? nullptr : curItems[selectionPos]; + return curItems.empty() || curItems[selectionPos]->isFolder ? nullptr : curItems[selectionPos]; } void SelectionTab::rememberCurrentSelection() { + if(getSelectedMapInfo()->isFolder) + return; + // TODO: this can be more elegant if(tabType == ESelectionScreen::newGame) { @@ -584,7 +717,7 @@ void SelectionTab::parseMaps(const std::unordered_set & files) { try { - auto mapInfo = std::make_shared(); + auto mapInfo = std::make_shared(); mapInfo->mapInit(file.getName()); if (isMapSupported(*mapInfo)) @@ -603,7 +736,7 @@ void SelectionTab::parseSaves(const std::unordered_set & files) { try { - auto mapInfo = std::make_shared(); + auto mapInfo = std::make_shared(); mapInfo->saveInit(file); // Filter out other game modes @@ -639,7 +772,7 @@ void SelectionTab::parseCampaigns(const std::unordered_set & files) allItems.reserve(files.size()); for(auto & file : files) { - auto info = std::make_shared(); + auto info = std::make_shared(); //allItems[i].date = std::asctime(std::localtime(&files[i].date)); info->fileURI = file.getName(); info->campaignInit(); @@ -668,6 +801,7 @@ SelectionTab::ListItem::ListItem(Point position, std::shared_ptr ico : CIntObject(LCLICK, position) { OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE; + pictureEmptyLine = std::make_shared(IImage::createFromFile("camcust"), Rect(25, 121, 349, 26), -8, -14); labelName = std::make_shared(184, 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE); labelName->setAutoRedraw(false); labelAmountOfPlayers = std::make_shared(8, 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE); @@ -677,17 +811,20 @@ SelectionTab::ListItem::ListItem(Point position, std::shared_ptr ico labelMapSizeLetter = std::make_shared(41, 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE); labelMapSizeLetter->setAutoRedraw(false); // FIXME: This -12 should not be needed, but for some reason CAnimImage displaced otherwise + iconFolder = std::make_shared("lobby/iconFolder.png", -8, -12); iconFormat = std::make_shared(iconsFormats, 0, 0, 59, -12); iconVictoryCondition = std::make_shared(iconsVictory, 0, 0, 277, -12); iconLossCondition = std::make_shared(iconsLoss, 0, 0, 310, -12); } -void SelectionTab::ListItem::updateItem(std::shared_ptr info, bool selected) +void SelectionTab::ListItem::updateItem(std::shared_ptr info, bool selected) { if(!info) { labelAmountOfPlayers->disable(); labelMapSizeLetter->disable(); + iconFolder->disable(); + pictureEmptyLine->disable(); iconFormat->disable(); iconVictoryCondition->disable(); iconLossCondition->disable(); @@ -697,10 +834,28 @@ void SelectionTab::ListItem::updateItem(std::shared_ptr info, bool sel } auto color = selected ? Colors::YELLOW : Colors::WHITE; + if(info->isFolder) + { + labelAmountOfPlayers->disable(); + labelMapSizeLetter->disable(); + iconFolder->enable(); + pictureEmptyLine->enable(); + iconFormat->disable(); + iconVictoryCondition->disable(); + iconLossCondition->disable(); + labelNumberOfCampaignMaps->disable(); + labelName->enable(); + labelName->setText(info->folderName); + labelName->setColor(color); + return; + } + if(info->campaign) { labelAmountOfPlayers->disable(); labelMapSizeLetter->disable(); + iconFolder->disable(); + pictureEmptyLine->disable(); iconFormat->disable(); iconVictoryCondition->disable(); iconLossCondition->disable(); @@ -721,6 +876,8 @@ void SelectionTab::ListItem::updateItem(std::shared_ptr info, bool sel labelMapSizeLetter->enable(); labelMapSizeLetter->setText(info->getMapSizeName()); labelMapSizeLetter->setColor(color); + iconFolder->disable(); + pictureEmptyLine->disable(); iconFormat->enable(); iconFormat->setFrame(info->getMapSizeFormatIconId()); iconVictoryCondition->enable(); diff --git a/client/lobby/SelectionTab.h b/client/lobby/SelectionTab.h index 74bc41233..5e8e35e92 100644 --- a/client/lobby/SelectionTab.h +++ b/client/lobby/SelectionTab.h @@ -10,6 +10,7 @@ #pragma once #include "CSelectionBase.h" +#include "../../lib/mapping/CMapInfo.h" class CSlider; class CLabel; @@ -19,12 +20,21 @@ enum ESortBy _playerAm, _size, _format, _name, _viccon, _loscon, _numOfMaps, _fileName }; //_numOfMaps is for campaigns +class ElementInfo : public CMapInfo +{ +public: + ElementInfo() : CMapInfo() { } + ~ElementInfo() { } + std::string folderName = ""; + bool isFolder = false; +}; + /// Class which handles map sorting by different criteria class mapSorter { public: ESortBy sortBy; - bool operator()(const std::shared_ptr aaa, const std::shared_ptr bbb); + bool operator()(const std::shared_ptr aaa, const std::shared_ptr bbb); mapSorter(ESortBy es) : sortBy(es){}; }; @@ -35,13 +45,15 @@ class SelectionTab : public CIntObject std::shared_ptr labelAmountOfPlayers; std::shared_ptr labelNumberOfCampaignMaps; std::shared_ptr labelMapSizeLetter; + std::shared_ptr iconFolder; std::shared_ptr iconFormat; std::shared_ptr iconVictoryCondition; std::shared_ptr iconLossCondition; + std::shared_ptr pictureEmptyLine; std::shared_ptr labelName; ListItem(Point position, std::shared_ptr iconsFormats, std::shared_ptr iconsVictory, std::shared_ptr iconsLoss); - void updateItem(std::shared_ptr info = {}, bool selected = false); + void updateItem(std::shared_ptr info = {}, bool selected = false); }; std::vector> listItems; @@ -51,14 +63,16 @@ class SelectionTab : public CIntObject std::shared_ptr iconsLossCondition; public: - std::vector> allItems; - std::vector> curItems; + std::vector> allItems; + std::vector> curItems; + std::string curFolder; size_t selectionPos; - std::function)> callOnSelect; + std::function)> callOnSelect; ESortBy sortingBy; ESortBy generalSortingBy; bool sortModeAscending; + int currentMapSizeFilter = 0; std::shared_ptr inputName; @@ -81,7 +95,7 @@ public: int getLine() const; int getLine(const Point & position) const; void selectFileName(std::string fname); - std::shared_ptr getSelectedMapInfo() const; + std::shared_ptr getSelectedMapInfo() const; void rememberCurrentSelection(); void restoreLastSelection(); @@ -95,6 +109,8 @@ private: ESelectionScreen tabType; Rect inputNameRect; + auto checkSubfolder(std::string path); + bool isMapSupported(const CMapInfo & info); void parseMaps(const std::unordered_set & files); void parseSaves(const std::unordered_set & files); diff --git a/cmake_modules/VCMI_lib.cmake b/cmake_modules/VCMI_lib.cmake index 7152a5327..ba34a1391 100644 --- a/cmake_modules/VCMI_lib.cmake +++ b/cmake_modules/VCMI_lib.cmake @@ -220,6 +220,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/spells/effects/RemoveObstacle.cpp ${MAIN_LIB_DIR}/spells/effects/Sacrifice.cpp + ${MAIN_LIB_DIR}/vstd/DateUtils.cpp ${MAIN_LIB_DIR}/vstd/StringUtils.cpp ${MAIN_LIB_DIR}/ArtifactUtils.cpp @@ -283,6 +284,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE) ${MAIN_LIB_DIR}/../include/vstd/ContainerUtils.h ${MAIN_LIB_DIR}/../include/vstd/RNG.h + ${MAIN_LIB_DIR}/../include/vstd/DateUtils.h ${MAIN_LIB_DIR}/../include/vstd/StringUtils.h ${MAIN_LIB_DIR}/../include/vcmi/events/AdventureEvents.h diff --git a/include/vstd/DateUtils.h b/include/vstd/DateUtils.h new file mode 100644 index 000000000..9f3a9cc43 --- /dev/null +++ b/include/vstd/DateUtils.h @@ -0,0 +1,12 @@ +#pragma once + +VCMI_LIB_NAMESPACE_BEGIN + +namespace vstd +{ + + DLL_LINKAGE std::string getFormattedDateTime(std::time_t dt); + +} + +VCMI_LIB_NAMESPACE_END diff --git a/lib/VCMI_lib.vcxproj b/lib/VCMI_lib.vcxproj index 2598a5bc6..ea6777a05 100644 --- a/lib/VCMI_lib.vcxproj +++ b/lib/VCMI_lib.vcxproj @@ -320,6 +320,7 @@ + diff --git a/lib/VCMI_lib.vcxproj.filters b/lib/VCMI_lib.vcxproj.filters index a8541fe9d..3679e6a8c 100644 --- a/lib/VCMI_lib.vcxproj.filters +++ b/lib/VCMI_lib.vcxproj.filters @@ -393,6 +393,9 @@ registerTypes + + vstd + vstd diff --git a/lib/filesystem/CFilesystemLoader.cpp b/lib/filesystem/CFilesystemLoader.cpp index f7266f5f2..d56f792be 100644 --- a/lib/filesystem/CFilesystemLoader.cpp +++ b/lib/filesystem/CFilesystemLoader.cpp @@ -85,6 +85,10 @@ bool CFilesystemLoader::createResource(std::string filename, bool update) if (!update) { + // create folders if not exists + boost::filesystem::path p((baseDirectory / filename).c_str()); + boost::filesystem::create_directories(p.parent_path()); + // create file, if not exists std::ofstream file((baseDirectory / filename).c_str(), std::ofstream::binary); diff --git a/lib/filesystem/Filesystem.cpp b/lib/filesystem/Filesystem.cpp index cf9bc4f2e..da8bba83e 100644 --- a/lib/filesystem/Filesystem.cpp +++ b/lib/filesystem/Filesystem.cpp @@ -147,7 +147,7 @@ ISimpleResourceLoader * CResourceHandler::createInitial() for (auto & path : VCMIDirs::get().dataPaths()) { if (boost::filesystem::is_directory(path)) // some of system-provided paths may not exist - initialLoader->addLoader(new CFilesystemLoader("", path, 0, true), false); + initialLoader->addLoader(new CFilesystemLoader("", path, 1, true), false); } initialLoader->addLoader(new CFilesystemLoader("", VCMIDirs::get().userDataPath(), 0, true), false); diff --git a/lib/filesystem/ResourceID.cpp b/lib/filesystem/ResourceID.cpp index fb63fc428..401d89ef1 100644 --- a/lib/filesystem/ResourceID.cpp +++ b/lib/filesystem/ResourceID.cpp @@ -45,7 +45,7 @@ static inline EResType::Type readType(const std::string& name) return EResTypeHelper::getTypeFromExtension(FileInfo::GetExtension(name).to_string()); } -static inline std::string readName(std::string name) +static inline std::string readName(std::string name, bool uppercase) { const auto dotPos = name.find_last_of('.'); @@ -61,7 +61,8 @@ static inline std::string readName(std::string name) name.resize(dotPos); } - toUpper(name); + if(uppercase) + toUpper(name); return name; } @@ -75,12 +76,14 @@ ResourceID::ResourceID() ResourceID::ResourceID(std::string name_): type{readType(name_)}, - name{readName(std::move(name_))} + name{readName(name_, true)}, + originalName{readName(std::move(name_), false)} {} ResourceID::ResourceID(std::string name_, EResType::Type type_): type{type_}, - name{readName(std::move(name_))} + name{readName(name_, true)}, + originalName{readName(std::move(name_), false)} {} #if 0 std::string ResourceID::getName() const diff --git a/lib/filesystem/ResourceID.h b/lib/filesystem/ResourceID.h index de29dec48..0e597bd74 100644 --- a/lib/filesystem/ResourceID.h +++ b/lib/filesystem/ResourceID.h @@ -99,6 +99,7 @@ public: } std::string getName() const {return name;} + std::string getOriginalName() const {return originalName;} EResType::Type getType() const {return type;} //void setName(std::string name); //void setType(EResType::Type type); @@ -112,6 +113,9 @@ private: /** Specifies the resource name. No extension so .pcx and .png can override each other, always in upper case. **/ std::string name; + + /** name in original case **/ + std::string originalName; }; /** diff --git a/lib/mapping/CMapInfo.cpp b/lib/mapping/CMapInfo.cpp index 734a80265..64796bb6f 100644 --- a/lib/mapping/CMapInfo.cpp +++ b/lib/mapping/CMapInfo.cpp @@ -10,6 +10,8 @@ #include "StdInc.h" #include "CMapInfo.h" +#include + #include "../filesystem/ResourceID.h" #include "../StartInfo.h" #include "../GameConstants.h" @@ -43,7 +45,10 @@ void CMapInfo::mapInit(const std::string & fname) { fileURI = fname; CMapService mapService; - mapHeader = mapService.loadMapHeader(ResourceID(fname, EResType::MAP)); + ResourceID resource = ResourceID(fname, EResType::MAP); + originalFileURI = resource.getOriginalName(); + fullFileURI = boost::filesystem::canonical(*CResourceHandler::get()->getResourceName(resource)).string(); + mapHeader = mapService.loadMapHeader(resource); countPlayers(); } @@ -55,9 +60,12 @@ void CMapInfo::saveInit(const ResourceID & file) mapHeader = std::make_unique(); lf >> *(mapHeader) >> scenarioOptionsOfSave; fileURI = file.getName(); + originalFileURI = file.getOriginalName(); + fullFileURI = boost::filesystem::canonical(*CResourceHandler::get()->getResourceName(file)).string(); countPlayers(); std::time_t time = boost::filesystem::last_write_time(*CResourceHandler::get()->getResourceName(file)); - date = std::asctime(std::localtime(&time)); + date = vstd::getFormattedDateTime(time); + // We absolutely not need this data for lobby and server will read it from save // FIXME: actually we don't want them in CMapHeader! mapHeader->triggeredEvents.clear(); @@ -65,6 +73,9 @@ void CMapInfo::saveInit(const ResourceID & file) void CMapInfo::campaignInit() { + ResourceID resource = ResourceID(fileURI, EResType::CAMPAIGN); + originalFileURI = resource.getOriginalName(); + fullFileURI = boost::filesystem::canonical(*CResourceHandler::get()->getResourceName(resource)).string(); campaign = CampaignHandler::getHeader(fileURI); } @@ -105,7 +116,7 @@ std::string CMapInfo::getNameForList() const { // TODO: this could be handled differently std::vector path; - boost::split(path, fileURI, boost::is_any_of("\\/")); + boost::split(path, originalFileURI, boost::is_any_of("\\/")); return path[path.size()-1]; } else diff --git a/lib/mapping/CMapInfo.h b/lib/mapping/CMapInfo.h index 5724988bd..219969c4a 100644 --- a/lib/mapping/CMapInfo.h +++ b/lib/mapping/CMapInfo.h @@ -28,6 +28,8 @@ public: std::unique_ptr campaign; //may be nullptr if scenario StartInfo * scenarioOptionsOfSave; // Options with which scenario has been started (used only with saved games) std::string fileURI; + std::string originalFileURI; + std::string fullFileURI; std::string date; int amountOfPlayersOnMap; int amountOfHumanControllablePlayers; diff --git a/lib/vstd/DateUtils.cpp b/lib/vstd/DateUtils.cpp new file mode 100644 index 000000000..41f2d59c7 --- /dev/null +++ b/lib/vstd/DateUtils.cpp @@ -0,0 +1,20 @@ +#include "StdInc.h" +#include + +VCMI_LIB_NAMESPACE_BEGIN + +namespace vstd +{ + + DLL_LINKAGE std::string getFormattedDateTime(std::time_t dt) + { + std::tm tm = *std::localtime(&dt); + std::stringstream s; + s.imbue(std::locale("")); + s << std::put_time(&tm, "%x %X"); + return s.str(); + } + +} + +VCMI_LIB_NAMESPACE_END