Răsfoiți Sursa

Merge pull request #2517 from Laserlicht/folders

Nordsoft91 2 ani în urmă
părinte
comite
96e820ddac

BIN
Mods/vcmi/Data/lobby/iconFolder.png


+ 1 - 1
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
 	{

+ 183 - 26
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<CMapInfo> aaa, const std::shared_ptr<CMapInfo> bbb)
+bool mapSorter::operator()(const std::shared_ptr<ElementInfo> aaa, const std::shared_ptr<ElementInfo> 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<CLobbyScreen *>(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<std::string> 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)
-			curItems.push_back(elem);
-	}
-	else
+	for(auto elem : allItems)
 	{
-		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<ElementInfo>();
+				folder->isFolder = true;
+				folder->folderName = "..     (" + curFolder + ")";
+				auto itemIt = boost::range::find_if(curItems, [](std::shared_ptr<ElementInfo> e) { return boost::starts_with(e->folderName, ".."); });
+				if (itemIt == curItems.end()) {
+					curItems.push_back(folder);
+				}			
+			}
+
+			std::shared_ptr<ElementInfo> folder = std::make_shared<ElementInfo>();
+			folder->isFolder = true;
+			folder->folderName = folderName;
+			auto itemIt = boost::range::find_if(curItems, [folder](std::shared_ptr<ElementInfo> 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<ElementInfo> 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<ElementInfo> 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<std::string> 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<ElementInfo> 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<ElementInfo> 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<CMapInfo> SelectionTab::getSelectedMapInfo() const
+std::shared_ptr<ElementInfo> 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<ResourceID> & files)
 	{
 		try
 		{
-			auto mapInfo = std::make_shared<CMapInfo>();
+			auto mapInfo = std::make_shared<ElementInfo>();
 			mapInfo->mapInit(file.getName());
 
 			if (isMapSupported(*mapInfo))
@@ -603,7 +736,7 @@ void SelectionTab::parseSaves(const std::unordered_set<ResourceID> & files)
 	{
 		try
 		{
-			auto mapInfo = std::make_shared<CMapInfo>();
+			auto mapInfo = std::make_shared<ElementInfo>();
 			mapInfo->saveInit(file);
 
 			// Filter out other game modes
@@ -639,7 +772,7 @@ void SelectionTab::parseCampaigns(const std::unordered_set<ResourceID> & files)
 	allItems.reserve(files.size());
 	for(auto & file : files)
 	{
-		auto info = std::make_shared<CMapInfo>();
+		auto info = std::make_shared<ElementInfo>();
 		//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<CAnimation> ico
 	: CIntObject(LCLICK, position)
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
+	pictureEmptyLine = std::make_shared<CPicture>(IImage::createFromFile("camcust"), Rect(25, 121, 349, 26), -8, -14);
 	labelName = std::make_shared<CLabel>(184, 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
 	labelName->setAutoRedraw(false);
 	labelAmountOfPlayers = std::make_shared<CLabel>(8, 0, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE);
@@ -677,17 +811,20 @@ SelectionTab::ListItem::ListItem(Point position, std::shared_ptr<CAnimation> ico
 	labelMapSizeLetter = std::make_shared<CLabel>(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<CPicture>("lobby/iconFolder.png", -8, -12);
 	iconFormat = std::make_shared<CAnimImage>(iconsFormats, 0, 0, 59, -12);
 	iconVictoryCondition = std::make_shared<CAnimImage>(iconsVictory, 0, 0, 277, -12);
 	iconLossCondition = std::make_shared<CAnimImage>(iconsLoss, 0, 0, 310, -12);
 }
 
-void SelectionTab::ListItem::updateItem(std::shared_ptr<CMapInfo> info, bool selected)
+void SelectionTab::ListItem::updateItem(std::shared_ptr<ElementInfo> 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<CMapInfo> 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<CMapInfo> info, bool sel
 		labelMapSizeLetter->enable();
 		labelMapSizeLetter->setText(info->getMapSizeName());
 		labelMapSizeLetter->setColor(color);
+		iconFolder->disable();
+		pictureEmptyLine->disable();
 		iconFormat->enable();
 		iconFormat->setFrame(info->getMapSizeFormatIconId());
 		iconVictoryCondition->enable();

+ 22 - 6
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<CMapInfo> aaa, const std::shared_ptr<CMapInfo> bbb);
+	bool operator()(const std::shared_ptr<ElementInfo> aaa, const std::shared_ptr<ElementInfo> bbb);
 	mapSorter(ESortBy es) : sortBy(es){};
 };
 
@@ -35,13 +45,15 @@ class SelectionTab : public CIntObject
 		std::shared_ptr<CLabel> labelAmountOfPlayers;
 		std::shared_ptr<CLabel> labelNumberOfCampaignMaps;
 		std::shared_ptr<CLabel> labelMapSizeLetter;
+		std::shared_ptr<CPicture> iconFolder;
 		std::shared_ptr<CAnimImage> iconFormat;
 		std::shared_ptr<CAnimImage> iconVictoryCondition;
 		std::shared_ptr<CAnimImage> iconLossCondition;
+		std::shared_ptr<CPicture> pictureEmptyLine;
 		std::shared_ptr<CLabel> labelName;
 
 		ListItem(Point position, std::shared_ptr<CAnimation> iconsFormats, std::shared_ptr<CAnimation> iconsVictory, std::shared_ptr<CAnimation> iconsLoss);
-		void updateItem(std::shared_ptr<CMapInfo> info = {}, bool selected = false);
+		void updateItem(std::shared_ptr<ElementInfo> info = {}, bool selected = false);
 	};
 	std::vector<std::shared_ptr<ListItem>> listItems;
 
@@ -51,14 +63,16 @@ class SelectionTab : public CIntObject
 	std::shared_ptr<CAnimation> iconsLossCondition;
 
 public:
-	std::vector<std::shared_ptr<CMapInfo>> allItems;
-	std::vector<std::shared_ptr<CMapInfo>> curItems;
+	std::vector<std::shared_ptr<ElementInfo>> allItems;
+	std::vector<std::shared_ptr<ElementInfo>> curItems;
+	std::string curFolder;
 	size_t selectionPos;
-	std::function<void(std::shared_ptr<CMapInfo>)> callOnSelect;
+	std::function<void(std::shared_ptr<ElementInfo>)> callOnSelect;
 
 	ESortBy sortingBy;
 	ESortBy generalSortingBy;
 	bool sortModeAscending;
+	int currentMapSizeFilter = 0;
 
 	std::shared_ptr<CTextInput> inputName;
 
@@ -81,7 +95,7 @@ public:
 	int getLine() const;
 	int getLine(const Point & position) const;
 	void selectFileName(std::string fname);
-	std::shared_ptr<CMapInfo> getSelectedMapInfo() const;
+	std::shared_ptr<ElementInfo> 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<ResourceID> & files);
 	void parseSaves(const std::unordered_set<ResourceID> & files);

+ 2 - 0
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

+ 12 - 0
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

+ 1 - 0
lib/VCMI_lib.vcxproj

@@ -320,6 +320,7 @@
     </ClCompile>
     <ClCompile Include="VCMIDirs.cpp" />
     <ClCompile Include="VCMI_Lib.cpp" />
+    <ClCompile Include="vstd\DateUtils.cpp" />
     <ClCompile Include="vstd\StringUtils.cpp" />
   </ItemGroup>
   <ItemGroup>

+ 3 - 0
lib/VCMI_lib.vcxproj.filters

@@ -393,6 +393,9 @@
     <ClCompile Include="registerTypes\TypesLobbyPacks.cpp">
       <Filter>registerTypes</Filter>
     </ClCompile>
+    <ClCompile Include="vstd\DateUtils.cpp">
+      <Filter>vstd</Filter>
+    </ClCompile>
     <ClCompile Include="vstd\StringUtils.cpp">
       <Filter>vstd</Filter>
     </ClCompile>

+ 4 - 0
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);
 

+ 1 - 1
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);
 

+ 7 - 4
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

+ 4 - 0
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;
 };
 
 /**

+ 14 - 3
lib/mapping/CMapInfo.cpp

@@ -10,6 +10,8 @@
 #include "StdInc.h"
 #include "CMapInfo.h"
 
+#include <vstd/DateUtils.h>
+
 #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<CMapHeader>();
 	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<std::string> path;
-		boost::split(path, fileURI, boost::is_any_of("\\/"));
+		boost::split(path, originalFileURI, boost::is_any_of("\\/"));
 		return path[path.size()-1];
 	}
 	else

+ 2 - 0
lib/mapping/CMapInfo.h

@@ -28,6 +28,8 @@ public:
 	std::unique_ptr<Campaign> 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;

+ 20 - 0
lib/vstd/DateUtils.cpp

@@ -0,0 +1,20 @@
+#include "StdInc.h"
+#include <vstd/DateUtils.h>
+
+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