Browse Source

Merge pull request #2757 from IvanSavenko/filesystem_refactor

Filesystem refactor - part 1
Ivan Savenko 2 years ago
parent
commit
f39fbe5151
100 changed files with 775 additions and 655 deletions
  1. 1 1
      AI/Nullkiller/AIGateway.cpp
  2. 1 1
      AI/Nullkiller/AIGateway.h
  3. 1 1
      AI/Nullkiller/Engine/PriorityEvaluator.cpp
  4. 1 1
      AI/VCAI/VCAI.cpp
  5. 1 1
      AI/VCAI/VCAI.h
  6. 4 4
      client/CMT.cpp
  7. 3 0
      client/CMakeLists.txt
  8. 39 35
      client/CMusicHandler.cpp
  9. 15 19
      client/CMusicHandler.h
  10. 12 13
      client/CPlayerInterface.cpp
  11. 1 1
      client/CPlayerInterface.h
  12. 1 1
      client/CServerHandler.cpp
  13. 12 9
      client/CVideoHandler.cpp
  14. 8 7
      client/CVideoHandler.h
  15. 1 1
      client/Client.cpp
  16. 7 6
      client/ClientCommandManager.cpp
  17. 20 24
      client/adventureMap/AdventureMapWidget.cpp
  18. 5 5
      client/adventureMap/AdventureMapWidget.h
  19. 6 6
      client/adventureMap/AdventureOptions.cpp
  20. 1 1
      client/adventureMap/CInGameConsole.cpp
  21. 19 19
      client/adventureMap/CInfoBar.cpp
  22. 2 1
      client/adventureMap/CInfoBar.h
  23. 10 10
      client/adventureMap/CList.cpp
  24. 1 1
      client/adventureMap/CMinimap.cpp
  25. 2 2
      client/adventureMap/CResDataBar.cpp
  26. 3 2
      client/adventureMap/CResDataBar.h
  27. 5 5
      client/adventureMap/MapAudioPlayer.cpp
  28. 2 1
      client/adventureMap/MapAudioPlayer.h
  29. 3 3
      client/adventureMap/TurnTimerWidget.cpp
  30. 20 19
      client/battle/BattleAnimationClasses.cpp
  31. 9 8
      client/battle/BattleAnimationClasses.h
  32. 11 11
      client/battle/BattleEffectsController.cpp
  33. 2 1
      client/battle/BattleEffectsController.h
  34. 11 10
      client/battle/BattleFieldController.cpp
  35. 4 4
      client/battle/BattleInterface.cpp
  36. 31 30
      client/battle/BattleInterfaceClasses.cpp
  37. 8 7
      client/battle/BattleObstacleController.cpp
  38. 3 1
      client/battle/BattleObstacleController.h
  39. 5 4
      client/battle/BattleProjectileController.cpp
  40. 3 2
      client/battle/BattleProjectileController.h
  41. 29 27
      client/battle/BattleSiegeController.cpp
  42. 3 2
      client/battle/BattleSiegeController.h
  43. 11 10
      client/battle/BattleStacksController.cpp
  44. 11 10
      client/battle/BattleWindow.cpp
  45. 5 3
      client/battle/CreatureAnimation.cpp
  46. 2 2
      client/battle/CreatureAnimation.h
  47. 7 0
      client/gui/CGuiHandler.cpp
  48. 3 1
      client/gui/CGuiHandler.h
  49. 9 8
      client/gui/CursorHandler.cpp
  50. 3 2
      client/gui/CursorHandler.h
  51. 10 10
      client/gui/InterfaceObjectConfigurable.cpp
  52. 13 13
      client/lobby/CBonusSelection.cpp
  53. 8 8
      client/lobby/CLobbyScreen.cpp
  54. 2 2
      client/lobby/CSavingScreen.cpp
  55. 1 1
      client/lobby/CScenarioInfoScreen.cpp
  56. 13 12
      client/lobby/CSelectionBase.cpp
  57. 25 25
      client/lobby/OptionsTab.cpp
  58. 1 1
      client/lobby/OptionsTab.h
  59. 2 2
      client/lobby/RandomMapTab.cpp
  60. 25 24
      client/lobby/SelectionTab.cpp
  61. 7 6
      client/lobby/SelectionTab.h
  62. 10 15
      client/mainmenu/CCampaignScreen.cpp
  63. 1 1
      client/mainmenu/CCampaignScreen.h
  64. 27 26
      client/mainmenu/CMainMenu.cpp
  65. 1 1
      client/mainmenu/CMainMenu.h
  66. 1 1
      client/mainmenu/CPrologEpilogVideo.cpp
  67. 1 1
      client/mainmenu/CreditsScreen.cpp
  68. 16 14
      client/mapView/MapRenderer.cpp
  69. 11 9
      client/mapView/MapRenderer.h
  70. 4 1
      client/mapView/MapViewCache.cpp
  71. 1 1
      client/mapView/MapViewCache.h
  72. 17 27
      client/render/CAnimation.cpp
  73. 4 2
      client/render/CAnimation.h
  74. 24 19
      client/render/CBitmapHandler.cpp
  75. 3 1
      client/render/CBitmapHandler.h
  76. 5 5
      client/render/CDefFile.cpp
  77. 2 1
      client/render/CDefFile.h
  78. 13 13
      client/render/Graphics.cpp
  79. 3 2
      client/render/Graphics.h
  80. 5 11
      client/render/IImage.h
  81. 38 0
      client/render/IRenderHandler.h
  82. 2 2
      client/renderSDL/CBitmapFont.cpp
  83. 2 2
      client/renderSDL/CBitmapFont.h
  84. 1 1
      client/renderSDL/CBitmapHanFont.cpp
  85. 1 1
      client/renderSDL/CTrueTypeFont.cpp
  86. 40 0
      client/renderSDL/RenderHandler.cpp
  87. 25 0
      client/renderSDL/RenderHandler.h
  88. 3 23
      client/renderSDL/SDLImage.cpp
  89. 1 1
      client/renderSDL/SDLImage.h
  90. 5 4
      client/widgets/Buttons.cpp
  91. 5 4
      client/widgets/Buttons.h
  92. 3 3
      client/widgets/CArtifactHolder.cpp
  93. 1 1
      client/widgets/CArtifactsOfHeroBackpack.cpp
  94. 2 2
      client/widgets/CArtifactsOfHeroBase.cpp
  95. 6 6
      client/widgets/CComponent.cpp
  96. 3 3
      client/widgets/CComponent.h
  97. 1 1
      client/widgets/CGarrisonInt.cpp
  98. 1 1
      client/widgets/CWindowWithArtifacts.cpp
  99. 1 1
      client/widgets/ComboBox.cpp
  100. 1 1
      client/widgets/ComboBox.h

+ 1 - 1
AI/Nullkiller/AIGateway.cpp

@@ -388,7 +388,7 @@ void AIGateway::heroCreated(const CGHeroInstance * h)
 	NET_EVENT_HANDLER;
 }
 
-void AIGateway::advmapSpellCast(const CGHeroInstance * caster, int spellID)
+void AIGateway::advmapSpellCast(const CGHeroInstance * caster, SpellID spellID)
 {
 	LOG_TRACE_PARAMS(logAi, "spellID '%i", spellID);
 	NET_EVENT_HANDLER;

+ 1 - 1
AI/Nullkiller/AIGateway.h

@@ -152,7 +152,7 @@ public:
 	void showHillFortWindow(const CGObjectInstance * object, const CGHeroInstance * visitor) override;
 	void playerBonusChanged(const Bonus & bonus, bool gain) override;
 	void heroCreated(const CGHeroInstance *) override;
-	void advmapSpellCast(const CGHeroInstance * caster, int spellID) override;
+	void advmapSpellCast(const CGHeroInstance * caster, SpellID spellID) override;
 	void showInfoDialog(EInfoWindowMode type, const std::string & text, const std::vector<Component> & components, int soundID) override;
 	void requestRealized(PackageApplied * pa) override;
 	void receivedResource() override;

+ 1 - 1
AI/Nullkiller/Engine/PriorityEvaluator.cpp

@@ -68,7 +68,7 @@ PriorityEvaluator::~PriorityEvaluator()
 
 void PriorityEvaluator::initVisitTile()
 {
-	auto file = CResourceHandler::get()->load(ResourceID("config/ai/object-priorities.txt"))->readAll();
+	auto file = CResourceHandler::get()->load(ResourcePath("config/ai/object-priorities.txt"))->readAll();
 	std::string str = std::string((char *)file.first.get(), file.second);
 	engine = fl::FllImporter().fromString(str);
 	armyLossPersentageVariable = engine->getInputVariable("armyLoss");

+ 1 - 1
AI/VCAI/VCAI.cpp

@@ -475,7 +475,7 @@ void VCAI::heroCreated(const CGHeroInstance * h)
 	NET_EVENT_HANDLER;
 }
 
-void VCAI::advmapSpellCast(const CGHeroInstance * caster, int spellID)
+void VCAI::advmapSpellCast(const CGHeroInstance * caster, SpellID spellID)
 {
 	LOG_TRACE_PARAMS(logAi, "spellID '%i", spellID);
 	NET_EVENT_HANDLER;

+ 1 - 1
AI/VCAI/VCAI.h

@@ -185,7 +185,7 @@ public:
 	void showHillFortWindow(const CGObjectInstance * object, const CGHeroInstance * visitor) override;
 	void playerBonusChanged(const Bonus & bonus, bool gain) override;
 	void heroCreated(const CGHeroInstance *) override;
-	void advmapSpellCast(const CGHeroInstance * caster, int spellID) override;
+	void advmapSpellCast(const CGHeroInstance * caster, SpellID spellID) override;
 	void showInfoDialog(EInfoWindowMode type, const std::string & text, const std::vector<Component> & components, int soundID) override;
 	void requestRealized(PackageApplied * pa) override;
 	void receivedResource() override;

+ 4 - 4
client/CMT.cpp

@@ -251,7 +251,7 @@ int main(int argc, char * argv[])
 	// Some basic data validation to produce better error messages in cases of incorrect install
 	auto testFile = [](std::string filename, std::string message)
 	{
-		if (!CResourceHandler::get()->existsResource(ResourceID(filename)))
+		if (!CResourceHandler::get()->existsResource(ResourcePath(filename)))
 			handleFatalError(message, false);
 	};
 
@@ -423,10 +423,10 @@ int main(int argc, char * argv[])
 //plays intro, ends when intro is over or button has been pressed (handles events)
 void playIntro()
 {
-	if(CCS->videoh->openAndPlayVideo("3DOLOGO.SMK", 0, 1, true, true))
+	if(CCS->videoh->openAndPlayVideo(VideoPath::builtin("3DOLOGO.SMK"), 0, 1, true, true))
 	{
-		if (CCS->videoh->openAndPlayVideo("NWCLOGO.SMK", 0, 1, true, true))
-			CCS->videoh->openAndPlayVideo("H3INTRO.SMK", 0, 1, true, true);
+		if (CCS->videoh->openAndPlayVideo(VideoPath::builtin("NWCLOGO.SMK"), 0, 1, true, true))
+			CCS->videoh->openAndPlayVideo(VideoPath::builtin("H3INTRO.SMK"), 0, 1, true, true);
 	}
 }
 

+ 3 - 0
client/CMakeLists.txt

@@ -84,6 +84,7 @@ set(client_SRCS
 	renderSDL/CTrueTypeFont.cpp
 	renderSDL/CursorHardware.cpp
 	renderSDL/CursorSoftware.cpp
+	renderSDL/RenderHandler.cpp
 	renderSDL/SDLImage.cpp
 	renderSDL/SDLImageLoader.cpp
 	renderSDL/SDLRWwrapper.cpp
@@ -235,6 +236,7 @@ set(client_HEADERS
 	render/IFont.h
 	render/IImage.h
 	render/IImageLoader.h
+	render/IRenderHandler.h
 	render/IScreenHandler.h
 
 	renderSDL/CBitmapFont.h
@@ -242,6 +244,7 @@ set(client_HEADERS
 	renderSDL/CTrueTypeFont.h
 	renderSDL/CursorHardware.h
 	renderSDL/CursorSoftware.h
+	renderSDL/RenderHandler.h
 	renderSDL/SDLImage.h
 	renderSDL/SDLImageLoader.h
 	renderSDL/SDLRWwrapper.h

+ 39 - 35
client/CMusicHandler.cpp

@@ -75,7 +75,7 @@ void CSoundHandler::onVolumeChange(const JsonNode &volumeNode)
 
 CSoundHandler::CSoundHandler():
 	listener(settings.listen["general"]["sound"]),
-	ambientConfig(JsonNode(ResourceID("config/ambientSounds.json")))
+	ambientConfig(JsonPath::builtin("config/ambientSounds.json"))
 {
 	listener(std::bind(&CSoundHandler::onVolumeChange, this, _1));
 
@@ -119,25 +119,25 @@ void CSoundHandler::release()
 }
 
 // Allocate an SDL chunk and cache it.
-Mix_Chunk *CSoundHandler::GetSoundChunk(std::string &sound, bool cache)
+Mix_Chunk *CSoundHandler::GetSoundChunk(const AudioPath & sound, bool cache)
 {
 	try
 	{
 		if (cache && soundChunks.find(sound) != soundChunks.end())
 			return soundChunks[sound].first;
 
-		auto data = CResourceHandler::get()->load(ResourceID(std::string("SOUNDS/") + sound, EResType::SOUND))->readAll();
+		auto data = CResourceHandler::get()->load(sound.addPrefix("SOUNDS/"))->readAll();
 		SDL_RWops *ops = SDL_RWFromMem(data.first.get(), (int)data.second);
 		Mix_Chunk *chunk = Mix_LoadWAV_RW(ops, 1);	// will free ops
 
 		if (cache)
-			soundChunks.insert(std::pair<std::string, CachedChunk>(sound, std::make_pair (chunk, std::move (data.first))));
+			soundChunks.insert({sound, std::make_pair (chunk, std::move (data.first))});
 
 		return chunk;
 	}
 	catch(std::exception &e)
 	{
-		logGlobal->warn("Cannot get sound %s chunk: %s", sound, e.what());
+		logGlobal->warn("Cannot get sound %s chunk: %s", sound.getOriginalName(), e.what());
 		return nullptr;
 	}
 }
@@ -153,7 +153,7 @@ int CSoundHandler::ambientDistToVolume(int distance) const
 	return volume * (int)ambientConfig["volume"].Integer() / 100;
 }
 
-void CSoundHandler::ambientStopSound(std::string soundId)
+void CSoundHandler::ambientStopSound(const AudioPath & soundId)
 {
 	stopSound(ambientChannels[soundId]);
 	setChannelVolume(ambientChannels[soundId], volume);
@@ -163,13 +163,13 @@ void CSoundHandler::ambientStopSound(std::string soundId)
 int CSoundHandler::playSound(soundBase::soundID soundID, int repeats)
 {
 	assert(soundID < soundBase::sound_after_last);
-	auto sound = sounds[soundID];
-	logGlobal->trace("Attempt to play sound %d with file name %s with cache", soundID, sound);
+	auto sound = AudioPath::builtin(sounds[soundID]);
+	logGlobal->trace("Attempt to play sound %d with file name %s with cache", soundID, sound.getOriginalName());
 
 	return playSound(sound, repeats, true);
 }
 
-int CSoundHandler::playSound(std::string sound, int repeats, bool cache)
+int CSoundHandler::playSound(const AudioPath & sound, int repeats, bool cache)
 {
 	if (!initialized || sound.empty())
 		return -1;
@@ -182,7 +182,7 @@ int CSoundHandler::playSound(std::string sound, int repeats, bool cache)
 		channel = Mix_PlayChannel(-1, chunk, repeats);
 		if (channel == -1)
 		{
-			logGlobal->error("Unable to play sound file %s , error %s", sound, Mix_GetError());
+			logGlobal->error("Unable to play sound file %s , error %s", sound.getOriginalName(), Mix_GetError());
 			if (!cache)
 				Mix_FreeChunk(chunk);
 		}
@@ -290,14 +290,14 @@ int CSoundHandler::ambientGetRange() const
 	return static_cast<int>(ambientConfig["range"].Integer());
 }
 
-void CSoundHandler::ambientUpdateChannels(std::map<std::string, int> soundsArg)
+void CSoundHandler::ambientUpdateChannels(std::map<AudioPath, int> soundsArg)
 {
 	boost::mutex::scoped_lock guard(mutex);
 
-	std::vector<std::string> stoppedSounds;
+	std::vector<AudioPath> stoppedSounds;
 	for(auto & pair : ambientChannels)
 	{
-		const std::string & soundId = pair.first;
+		const auto & soundId = pair.first;
 		const int channel = pair.second;
 
 		if(!vstd::contains(soundsArg, soundId))
@@ -320,7 +320,7 @@ void CSoundHandler::ambientUpdateChannels(std::map<std::string, int> soundsArg)
 
 	for(auto & pair : soundsArg)
 	{
-		const std::string & soundId = pair.first;
+		const auto & soundId = pair.first;
 		const int distance = pair.second;
 
 		if(!vstd::contains(ambientChannels, soundId))
@@ -357,7 +357,7 @@ CMusicHandler::CMusicHandler():
 {
 	listener(std::bind(&CMusicHandler::onVolumeChange, this, _1));
 
-	auto mp3files = CResourceHandler::get()->getFilteredFiles([](const ResourceID & id) ->  bool
+	auto mp3files = CResourceHandler::get()->getFilteredFiles([](const ResourcePath & id) ->  bool
 	{
 		if(id.getType() != EResType::SOUND)
 			return false;
@@ -369,12 +369,12 @@ CMusicHandler::CMusicHandler():
 		return true;
 	});
 
-	for(const ResourceID & file : mp3files)
+	for(const ResourcePath & file : mp3files)
 	{
 		if(boost::algorithm::istarts_with(file.getName(), "MUSIC/Combat"))
-			addEntryToSet("battle", file.getName());
+			addEntryToSet("battle", AudioPath::fromResource(file));
 		else if(boost::algorithm::istarts_with(file.getName(), "MUSIC/AITheme"))
-			addEntryToSet("enemy-turn", file.getName());
+			addEntryToSet("enemy-turn", AudioPath::fromResource(file));
 	}
 
 }
@@ -383,11 +383,11 @@ void CMusicHandler::loadTerrainMusicThemes()
 {
 	for (const auto & terrain : CGI->terrainTypeHandler->objects)
 	{
-		addEntryToSet("terrain_" + terrain->getJsonKey(), "Music/" + terrain->musicFilename);
+		addEntryToSet("terrain_" + terrain->getJsonKey(), terrain->musicFilename);
 	}
 }
 
-void CMusicHandler::addEntryToSet(const std::string & set, const std::string & musicURI)
+void CMusicHandler::addEntryToSet(const std::string & set, const AudioPath & musicURI)
 {
 	musicsSet[set].push_back(musicURI);
 }
@@ -421,7 +421,7 @@ void CMusicHandler::release()
 	CAudioBase::release();
 }
 
-void CMusicHandler::playMusic(const std::string & musicURI, bool loop, bool fromStart)
+void CMusicHandler::playMusic(const AudioPath & musicURI, bool loop, bool fromStart)
 {
 	boost::mutex::scoped_lock guard(mutex);
 
@@ -451,7 +451,7 @@ void CMusicHandler::playMusicFromSet(const std::string & whichSet, bool loop, bo
 		return;
 
 	// in this mode - play random track from set
-	queueNext(this, whichSet, "", loop, fromStart);
+	queueNext(this, whichSet, AudioPath(), loop, fromStart);
 }
 
 void CMusicHandler::queueNext(std::unique_ptr<MusicEntry> queued)
@@ -468,7 +468,7 @@ void CMusicHandler::queueNext(std::unique_ptr<MusicEntry> queued)
 	}
 }
 
-void CMusicHandler::queueNext(CMusicHandler *owner, const std::string & setName, const std::string & musicURI, bool looped, bool fromStart)
+void CMusicHandler::queueNext(CMusicHandler *owner, const std::string & setName, const AudioPath & musicURI, bool looped, bool fromStart)
 {
 	queueNext(std::make_unique<MusicEntry>(owner, setName, musicURI, looped, fromStart));
 }
@@ -523,7 +523,7 @@ void CMusicHandler::musicFinishedCallback()
 	});
 }
 
-MusicEntry::MusicEntry(CMusicHandler *owner, std::string setName, std::string musicURI, bool looped, bool fromStart):
+MusicEntry::MusicEntry(CMusicHandler *owner, std::string setName, const AudioPath & musicURI, bool looped, bool fromStart):
 	owner(owner),
 	music(nullptr),
 	playing(false),
@@ -552,39 +552,43 @@ MusicEntry::~MusicEntry()
 		Mix_HaltMusic();
 	}
 
-	logGlobal->trace("Del-ing music file %s", currentName);
+	logGlobal->trace("Del-ing music file %s", currentName.getOriginalName());
 	if (music)
 		Mix_FreeMusic(music);
 }
 
-void MusicEntry::load(std::string musicURI)
+void MusicEntry::load(const AudioPath & musicURI)
 {
 	if (music)
 	{
-		logGlobal->trace("Del-ing music file %s", currentName);
+		logGlobal->trace("Del-ing music file %s", currentName.getOriginalName());
 		Mix_FreeMusic(music);
 		music = nullptr;
 	}
 
-	currentName = musicURI;
+	if (CResourceHandler::get()->existsResource(musicURI))
+		currentName = musicURI;
+	else
+		currentName = musicURI.addPrefix("MUSIC/");
+
 	music = nullptr;
 
-	logGlobal->trace("Loading music file %s", musicURI);
+	logGlobal->trace("Loading music file %s", currentName.getOriginalName());
 
 	try
 	{
-		auto musicFile = MakeSDLRWops(CResourceHandler::get()->load(ResourceID(std::move(musicURI), EResType::SOUND)));
+		auto musicFile = MakeSDLRWops(CResourceHandler::get()->load(currentName));
 		music = Mix_LoadMUS_RW(musicFile, SDL_TRUE);
 	}
 	catch(std::exception &e)
 	{
-		logGlobal->error("Failed to load music. setName=%s\tmusicURI=%s", setName, musicURI);
+		logGlobal->error("Failed to load music. setName=%s\tmusicURI=%s", setName, currentName.getOriginalName());
 		logGlobal->error("Exception: %s", e.what());
 	}
 
 	if(!music)
 	{
-		logGlobal->warn("Warning: Cannot open %s: %s", currentName, Mix_GetError());
+		logGlobal->warn("Warning: Cannot open %s: %s", currentName.getOriginalName(), Mix_GetError());
 		return;
 	}
 }
@@ -601,7 +605,7 @@ bool MusicEntry::play()
 		load(*iter);
 	}
 
-	logGlobal->trace("Playing music file %s", currentName);
+	logGlobal->trace("Playing music file %s", currentName.getOriginalName());
 
 	if (!fromStart && owner->trackPositions.count(currentName) > 0 && owner->trackPositions[currentName] > 0)
 	{
@@ -646,7 +650,7 @@ bool MusicEntry::stop(int fade_ms)
 		assert(startTime != uint32_t(-1));
 		float playDuration = (endTime - startTime + startPosition) / 1000.f;
 		owner->trackPositions[currentName] = playDuration;
-		logGlobal->trace("Stopping music file %s at %f", currentName, playDuration);
+		logGlobal->trace("Stopping music file %s at %f", currentName.getOriginalName(), playDuration);
 
 		Mix_FadeOutMusic(fade_ms);
 		return true;
@@ -664,7 +668,7 @@ bool MusicEntry::isSet(std::string set)
 	return !setName.empty() && set == setName;
 }
 
-bool MusicEntry::isTrack(std::string track)
+bool MusicEntry::isTrack(const AudioPath & track)
 {
 	return setName.empty() && track == currentName;
 }

+ 15 - 19
client/CMusicHandler.h

@@ -35,15 +35,14 @@ public:
 class CSoundHandler: public CAudioBase
 {
 private:
-	//soundBase::soundID getSoundID(const std::string &fileName);
 	//update volume on configuration change
 	SettingsListener listener;
 	void onVolumeChange(const JsonNode &volumeNode);
 
 	using CachedChunk = std::pair<Mix_Chunk *, std::unique_ptr<ui8[]>>;
-	std::map<std::string, CachedChunk> soundChunks;
+	std::map<AudioPath, CachedChunk> soundChunks;
 
-	Mix_Chunk *GetSoundChunk(std::string &sound, bool cache);
+	Mix_Chunk *GetSoundChunk(const AudioPath & sound, bool cache);
 
 	/// have entry for every currently active channel
 	/// vector will be empty if callback was not set
@@ -54,12 +53,12 @@ private:
 	boost::mutex mutexCallbacks;
 
 	int ambientDistToVolume(int distance) const;
-	void ambientStopSound(std::string soundId);
+	void ambientStopSound(const AudioPath & soundId);
 	void updateChannelVolume(int channel);
 
 	const JsonNode ambientConfig;
 
-	std::map<std::string, int> ambientChannels;
+	std::map<AudioPath, int> ambientChannels;
 	std::map<int, int> channelVolumes;
 
 	void initCallback(int channel, const std::function<void()> & function);
@@ -76,7 +75,7 @@ public:
 
 	// Sounds
 	int playSound(soundBase::soundID soundID, int repeats=0);
-	int playSound(std::string sound, int repeats=0, bool cache=false);
+	int playSound(const AudioPath & sound, int repeats=0, bool cache=false);
 	int playSoundFromSet(std::vector<soundBase::soundID> &sound_vec);
 	void stopSound(int handler);
 
@@ -84,16 +83,13 @@ public:
 	void soundFinishedCallback(int channel);
 
 	int ambientGetRange() const;
-	void ambientUpdateChannels(std::map<std::string, int> currentSounds);
+	void ambientUpdateChannels(std::map<AudioPath, int> currentSounds);
 	void ambientStopAllChannels();
 
 	// Sets
 	std::vector<soundBase::soundID> battleIntroSounds;
 };
 
-// Helper //now it looks somewhat useless
-#define battle_sound(creature,what_sound) creature->sounds.what_sound
-
 class CMusicHandler;
 
 //Class for handling one music file
@@ -109,16 +105,16 @@ class MusicEntry
 	uint32_t startPosition;
 	//if not null - set from which music will be randomly selected
 	std::string setName;
-	std::string currentName;
+	AudioPath currentName;
 
-	void load(std::string musicURI);
+	void load(const AudioPath & musicURI);
 
 public:
-	MusicEntry(CMusicHandler *owner, std::string setName, std::string musicURI, bool looped, bool fromStart);
+	MusicEntry(CMusicHandler *owner, std::string setName, const AudioPath & musicURI, bool looped, bool fromStart);
 	~MusicEntry();
 
 	bool isSet(std::string setName);
-	bool isTrack(std::string trackName);
+	bool isTrack(const AudioPath & trackName);
 	bool isPlaying();
 
 	bool play();
@@ -135,20 +131,20 @@ private:
 	std::unique_ptr<MusicEntry> current;
 	std::unique_ptr<MusicEntry> next;
 
-	void queueNext(CMusicHandler *owner, const std::string & setName, const std::string & musicURI, bool looped, bool fromStart);
+	void queueNext(CMusicHandler *owner, const std::string & setName, const AudioPath & musicURI, bool looped, bool fromStart);
 	void queueNext(std::unique_ptr<MusicEntry> queued);
 	void musicFinishedCallback();
 
 	/// map <set name> -> <list of URI's to tracks belonging to the said set>
-	std::map<std::string, std::vector<std::string>> musicsSet;
+	std::map<std::string, std::vector<AudioPath>> musicsSet;
 	/// stored position, in seconds at which music player should resume playing this track
-	std::map<std::string, float> trackPositions;
+	std::map<AudioPath, float> trackPositions;
 
 public:
 	CMusicHandler();
 
 	/// add entry with URI musicURI in set. Track will have ID musicID
-	void addEntryToSet(const std::string & set, const std::string & musicURI);
+	void addEntryToSet(const std::string & set, const AudioPath & musicURI);
 
 	void init() override;
 	void loadTerrainMusicThemes();
@@ -156,7 +152,7 @@ public:
 	void setVolume(ui32 percent) override;
 
 	/// play track by URI, if loop = true music will be looped
-	void playMusic(const std::string & musicURI, bool loop, bool fromStart);
+	void playMusic(const AudioPath & musicURI, bool loop, bool fromStart);
 	/// play random track from this set
 	void playMusicFromSet(const std::string & musicSet, bool loop, bool fromStart);
 	/// play random track from set (musicSet, entryID)

+ 12 - 13
client/CPlayerInterface.cpp

@@ -1112,11 +1112,11 @@ void CPlayerInterface::showBlockingDialog( const std::string &text, const std::v
 		for (auto & component : components)
 			intComps.push_back(std::make_shared<CSelectableComponent>(component)); //will be deleted by CSelWindow::close
 
-		std::vector<std::pair<std::string,CFunctionList<void()> > > pom;
-		pom.push_back(std::pair<std::string,CFunctionList<void()> >("IOKAY.DEF",0));
+		std::vector<std::pair<AnimationPath,CFunctionList<void()> > > pom;
+		pom.push_back({ AnimationPath::builtin("IOKAY.DEF"),0});
 		if (cancel)
 		{
-			pom.push_back(std::pair<std::string,CFunctionList<void()> >("ICANCEL.DEF",0));
+			pom.push_back({AnimationPath::builtin("ICANCEL.DEF"),0});
 		}
 
 		int charperline = 35;
@@ -1657,7 +1657,7 @@ void CPlayerInterface::viewWorldMap()
 	adventureInt->openWorldView();
 }
 
-void CPlayerInterface::advmapSpellCast(const CGHeroInstance * caster, int spellID)
+void CPlayerInterface::advmapSpellCast(const CGHeroInstance * caster, SpellID spellID)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 
@@ -1667,8 +1667,7 @@ void CPlayerInterface::advmapSpellCast(const CGHeroInstance * caster, int spellI
 	if(spellID == SpellID::FLY || spellID == SpellID::WATER_WALK)
 		localState->erasePath(caster);
 
-	const spells::Spell * spell = CGI->spells()->getByIndex(spellID);
-	auto castSoundPath = spell->getCastSound();
+	auto castSoundPath = spellID.toSpell()->getCastSound();
 	if(!castSoundPath.empty())
 		CCS->soundh->playSound(castSoundPath);
 }
@@ -1992,22 +1991,22 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
 			elem.coord = h->convertFromVisitablePos(elem.coord);
 
 		int soundChannel = -1;
-		std::string soundName;
+		AudioPath soundName;
 
-		auto getMovementSoundFor = [&](const CGHeroInstance * hero, int3 posPrev, int3 posNext, EPathNodeAction moveType) -> std::string
+		auto getMovementSoundFor = [&](const CGHeroInstance * hero, int3 posPrev, int3 posNext, EPathNodeAction moveType) -> AudioPath
 		{
 			if (moveType == EPathNodeAction::TELEPORT_BATTLE || moveType == EPathNodeAction::TELEPORT_BLOCKING_VISIT || moveType == EPathNodeAction::TELEPORT_NORMAL)
-				return "";
+				return {};
 
 			if (moveType == EPathNodeAction::EMBARK || moveType == EPathNodeAction::DISEMBARK)
-				return "";
+				return {};
 
 			if (moveType == EPathNodeAction::BLOCKING_VISIT)
-				return "";
+				return {};
 
 			// flying movement sound
 			if (hero->hasBonusOfType(BonusType::FLYING_MOVEMENT))
-				return "HORSE10.wav";
+				return AudioPath::builtin("HORSE10.wav");
 
 			auto prevTile = cb->getTile(h->convertToVisitablePos(posPrev));
 			auto nextTile = cb->getTile(h->convertToVisitablePos(posNext));
@@ -2073,7 +2072,7 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
 
 			{
 				// Start a new sound for the hero movement or let the existing one carry on.
-				std::string newSoundName = getMovementSoundFor(h, prevCoord, nextCoord, path.nodes[i-1].action);
+				AudioPath newSoundName = getMovementSoundFor(h, prevCoord, nextCoord, path.nodes[i-1].action);
 
 				if(newSoundName != soundName)
 				{

+ 1 - 1
client/CPlayerInterface.h

@@ -128,7 +128,7 @@ protected: // Call-ins from server, should not be called directly, but only via
 	void showMarketWindow(const IMarket *market, const CGHeroInstance *visitor) override;
 	void showUniversityWindow(const IMarket *market, const CGHeroInstance *visitor) override;
 	void showHillFortWindow(const CGObjectInstance *object, const CGHeroInstance *visitor) override;
-	void advmapSpellCast(const CGHeroInstance * caster, int spellID) override; //called when a hero casts a spell
+	void advmapSpellCast(const CGHeroInstance * caster, SpellID spellID) override; //called when a hero casts a spell
 	void tileHidden(const std::unordered_set<int3> &pos) override; //called when given tiles become hidden under fog of war
 	void tileRevealed(const std::unordered_set<int3> &pos) override; //called when fog of war disappears from given tiles
 	void newObject(const CGObjectInstance * obj) override;

+ 1 - 1
client/CServerHandler.cpp

@@ -748,7 +748,7 @@ void CServerHandler::debugStartTest(std::string filename, bool save)
 	if(save)
 	{
 		resetStateForLobby(StartInfo::LOAD_GAME);
-		mapInfo->saveInit(ResourceID(filename, EResType::SAVEGAME));
+		mapInfo->saveInit(ResourcePath(filename, EResType::SAVEGAME));
 		screenType = ESelectionScreen::loadGame;
 	}
 	else

+ 12 - 9
client/CVideoHandler.cpp

@@ -70,30 +70,32 @@ CVideoPlayer::CVideoPlayer()
 	, doLoop(false)
 {}
 
-bool CVideoPlayer::open(std::string fname, bool scale)
+bool CVideoPlayer::open(const VideoPath & fname, bool scale)
 {
 	return open(fname, true, false);
 }
 
 // loop = to loop through the video
 // useOverlay = directly write to the screen.
-bool CVideoPlayer::open(std::string fname, bool loop, bool useOverlay, bool scale)
+bool CVideoPlayer::open(const VideoPath & videoToOpen, bool loop, bool useOverlay, bool scale)
 {
 	close();
 
-	this->fname = fname;
 	doLoop = loop;
 	frameTime = 0;
 
-	ResourceID resource(std::string("Video/") + fname, EResType::VIDEO);
+	if (CResourceHandler::get()->existsResource(videoToOpen))
+		fname = videoToOpen;
+	else
+		fname = videoToOpen.addPrefix("VIDEO/");
 
-	if (!CResourceHandler::get()->existsResource(resource))
+	if (!CResourceHandler::get()->existsResource(fname))
 	{
-		logGlobal->error("Error: video %s was not found", resource.getName());
+		logGlobal->error("Error: video %s was not found", fname.getName());
 		return false;
 	}
 
-	data = CResourceHandler::get()->load(resource);
+	data = CResourceHandler::get()->load(fname);
 
 	static const int BUFFER_SIZE = 4096;
 
@@ -382,7 +384,8 @@ void CVideoPlayer::update( int x, int y, SDL_Surface *dst, bool forceRedraw, boo
 
 void CVideoPlayer::close()
 {
-	fname.clear();
+	fname = VideoPath();
+
 	if (sws)
 	{
 		sws_freeContext(sws);
@@ -467,7 +470,7 @@ bool CVideoPlayer::playVideo(int x, int y, bool stopOnKey)
 	return true;
 }
 
-bool CVideoPlayer::openAndPlayVideo(std::string name, int x, int y, bool stopOnKey, bool scale)
+bool CVideoPlayer::openAndPlayVideo(const VideoPath & name, int x, int y, bool stopOnKey, bool scale)
 {
 	open(name, false, true, scale);
 	bool ret = playVideo(x, y,  stopOnKey);

+ 8 - 7
client/CVideoHandler.h

@@ -10,6 +10,7 @@
 #pragma once
 
 #include "../lib/Rect.h"
+#include "../lib/filesystem/ResourcePath.h"
 
 struct SDL_Surface;
 struct SDL_Texture;
@@ -17,7 +18,7 @@ struct SDL_Texture;
 class IVideoPlayer
 {
 public:
-	virtual bool open(std::string name, bool scale = false)=0; //true - succes
+	virtual bool open(const VideoPath & name, bool scale = false)=0; //true - succes
 	virtual void close()=0;
 	virtual bool nextFrame()=0;
 	virtual void show(int x, int y, SDL_Surface *dst, bool update = true)=0;
@@ -30,10 +31,10 @@ public:
 class IMainVideoPlayer : public IVideoPlayer
 {
 public:
-	std::string fname;  //name of current video file (empty if idle)
+	VideoPath fname;  //name of current video file (empty if idle)
 
 	virtual void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update = true){}
-	virtual bool openAndPlayVideo(std::string name, int x, int y, bool stopOnKey = false, bool scale = false)
+	virtual bool openAndPlayVideo(const VideoPath & name, int x, int y, bool stopOnKey = false, bool scale = false)
 	{
 		return false;
 	}
@@ -49,7 +50,7 @@ public:
 	bool nextFrame() override {return false;};
 	void close() override {};
 	bool wait() override {return false;};
-	bool open(std::string name, bool scale = false) override {return false;};
+	bool open(const VideoPath & name, bool scale = false) override {return false;};
 };
 
 #ifndef DISABLE_VIDEO
@@ -85,14 +86,14 @@ class CVideoPlayer : public IMainVideoPlayer
 	bool doLoop;				// loop through video
 
 	bool playVideo(int x, int y, bool stopOnKey);
-	bool open(std::string fname, bool loop, bool useOverlay = false, bool scale = false);
+	bool open(const VideoPath & fname, bool loop, bool useOverlay = false, bool scale = false);
 
 public:
 	CVideoPlayer();
 	~CVideoPlayer();
 
 	bool init();
-	bool open(std::string fname, bool scale = false) override;
+	bool open(const VideoPath & fname, bool scale = false) override;
 	void close() override;
 	bool nextFrame() override;			// display next frame
 
@@ -101,7 +102,7 @@ public:
 	void update(int x, int y, SDL_Surface *dst, bool forceRedraw, bool update = true) override; //moves to next frame if appropriate, and blits it or blits only if redraw parameter is set true
 
 	// Opens video, calls playVideo, closes video; returns playVideo result (if whole video has been played)
-	bool openAndPlayVideo(std::string name, int x, int y, bool stopOnKey = false, bool scale = false) override;
+	bool openAndPlayVideo(const VideoPath & name, int x, int y, bool stopOnKey = false, bool scale = false) override;
 
 	//TODO:
 	bool wait() override {return false;};

+ 1 - 1
client/Client.cpp

@@ -222,7 +222,7 @@ void CClient::loadGame(CGameState * initializedGameState)
 	// try to deserialize client data including sleepingHeroes
 	try
 	{
-		boost::filesystem::path clientSaveName = *CResourceHandler::get()->getResourceName(ResourceID(CSH->si->mapname, EResType::CLIENT_SAVEGAME));
+		boost::filesystem::path clientSaveName = *CResourceHandler::get()->getResourceName(ResourcePath(CSH->si->mapname, EResType::CLIENT_SAVEGAME));
 
 		if(clientSaveName.empty())
 			throw std::runtime_error("Cannot open client part of " + CSH->si->mapname);

+ 7 - 6
client/ClientCommandManager.cpp

@@ -17,6 +17,7 @@
 #include "CServerHandler.h"
 #include "gui/CGuiHandler.h"
 #include "gui/WindowHandler.h"
+#include "render/IRenderHandler.h"
 #include "../lib/NetPacks.h"
 #include "ClientNetPackVisitors.h"
 #include "../lib/CConfigHandler.h"
@@ -182,12 +183,12 @@ void ClientCommandManager::handleNotDialogCommand()
 void ClientCommandManager::handleConvertTextCommand()
 {
 	logGlobal->info("Searching for available maps");
-	std::unordered_set<ResourceID> mapList = CResourceHandler::get()->getFilteredFiles([&](const ResourceID & ident)
+	std::unordered_set<ResourcePath> mapList = CResourceHandler::get()->getFilteredFiles([&](const ResourcePath & ident)
 	{
 		return ident.getType() == EResType::MAP;
 	});
 
-	std::unordered_set<ResourceID> campaignList = CResourceHandler::get()->getFilteredFiles([&](const ResourceID & ident)
+	std::unordered_set<ResourcePath> campaignList = CResourceHandler::get()->getFilteredFiles([&](const ResourcePath & ident)
 	{
 		return ident.getType() == EResType::CAMPAIGN;
 	});
@@ -292,7 +293,7 @@ void ClientCommandManager::handleGetTextCommand()
 			VCMIDirs::get().userExtractedPath();
 
 	auto list =
-			CResourceHandler::get()->getFilteredFiles([](const ResourceID & ident)
+			CResourceHandler::get()->getFilteredFiles([](const ResourcePath & ident)
 			{
 				return ident.getType() == EResType::TEXT && boost::algorithm::starts_with(ident.getName(), "DATA/");
 			});
@@ -317,7 +318,7 @@ void ClientCommandManager::handleDef2bmpCommand(std::istringstream& singleWordBu
 {
 	std::string URI;
 	singleWordBuffer >> URI;
-	std::unique_ptr<CAnimation> anim = std::make_unique<CAnimation>(URI);
+	auto anim = GH.renderHandler().loadAnimation(AnimationPath::builtin(URI));
 	anim->preload();
 	anim->exportBitmaps(VCMIDirs::get().userExtractedPath());
 }
@@ -327,11 +328,11 @@ void ClientCommandManager::handleExtractCommand(std::istringstream& singleWordBu
 	std::string URI;
 	singleWordBuffer >> URI;
 
-	if(CResourceHandler::get()->existsResource(ResourceID(URI)))
+	if(CResourceHandler::get()->existsResource(ResourcePath(URI)))
 	{
 		const boost::filesystem::path outPath = VCMIDirs::get().userExtractedPath() / URI;
 
-		auto data = CResourceHandler::get()->load(ResourceID(URI))->readAll();
+		auto data = CResourceHandler::get()->load(ResourcePath(URI))->readAll();
 
 		boost::filesystem::create_directories(outPath.parent_path());
 		std::ofstream outFile(outPath.c_str(), std::ofstream::binary);

+ 20 - 24
client/adventureMap/AdventureMapWidget.cpp

@@ -22,6 +22,7 @@
 #include "../mapView/MapView.h"
 #include "../render/CAnimation.h"
 #include "../render/IImage.h"
+#include "../render/IRenderHandler.h"
 #include "../widgets/Buttons.h"
 #include "../widgets/Images.h"
 #include "../widgets/TextControls.h"
@@ -30,7 +31,7 @@
 #include "../PlayerLocalState.h"
 
 #include "../../lib/constants/StringConstants.h"
-#include "../../lib/filesystem/ResourceID.h"
+#include "../../lib/filesystem/ResourcePath.h"
 
 AdventureMapWidget::AdventureMapWidget( std::shared_ptr<AdventureMapShortcuts> shortcuts )
 	: shortcuts(shortcuts)
@@ -56,13 +57,10 @@ AdventureMapWidget::AdventureMapWidget( std::shared_ptr<AdventureMapShortcuts> s
 	for (const auto & entry : shortcuts->getShortcuts())
 		addShortcut(entry.shortcut, entry.callback);
 
-	const JsonNode config(ResourceID("config/widgets/adventureMap.json"));
+	const JsonNode config(JsonPath::builtin("config/widgets/adventureMap.json"));
 
 	for(const auto & entry : config["options"]["imagesPlayerColored"].Vector())
-	{
-		ResourceID resourceName(entry.String(), EResType::IMAGE);
-		playerColorerImages.push_back(resourceName.getName());
-	}
+		playerColorerImages.push_back(ImagePath::fromJson(entry));
 
 	build(config);
 	addUsedEvents(KEYBOARD);
@@ -127,24 +125,24 @@ Rect AdventureMapWidget::readArea(const JsonNode & source, const Rect & bounding
 	return Rect(topLeft + boundingBox.topLeft(), dimensions);
 }
 
-std::shared_ptr<IImage> AdventureMapWidget::loadImage(const std::string & name)
+std::shared_ptr<IImage> AdventureMapWidget::loadImage(const JsonNode & name)
 {
-	ResourceID resource(name, EResType::IMAGE);
+	ImagePath resource = ImagePath::fromJson(name);
 
-	if(images.count(resource.getName()) == 0)
-		images[resource.getName()] = IImage::createFromFile(resource.getName());
+	if(images.count(resource) == 0)
+		images[resource] = GH.renderHandler().loadImage(resource);
 
-	return images[resource.getName()];
+	return images[resource];
 }
 
-std::shared_ptr<CAnimation> AdventureMapWidget::loadAnimation(const std::string & name)
+std::shared_ptr<CAnimation> AdventureMapWidget::loadAnimation(const JsonNode & name)
 {
-	ResourceID resource(name, EResType::ANIMATION);
+	AnimationPath resource = AnimationPath::fromJson(name);
 
-	if(animations.count(resource.getName()) == 0)
-		animations[resource.getName()] = std::make_shared<CAnimation>(resource.getName());
+	if(animations.count(resource) == 0)
+		animations[resource] = GH.renderHandler().loadAnimation(resource);
 
-	return animations[resource.getName()];
+	return animations[resource];
 }
 
 std::shared_ptr<CIntObject> AdventureMapWidget::buildInfobox(const JsonNode & input)
@@ -158,15 +156,14 @@ std::shared_ptr<CIntObject> AdventureMapWidget::buildMapImage(const JsonNode & i
 {
 	Rect targetArea = readTargetArea(input["area"]);
 	Rect sourceArea = readSourceArea(input["sourceArea"], input["area"]);
-	std::string image = input["image"].String();
 
-	return std::make_shared<CFilledTexture>(loadImage(image), targetArea, sourceArea);
+	return std::make_shared<CFilledTexture>(loadImage(input["image"]), targetArea, sourceArea);
 }
 
 std::shared_ptr<CIntObject> AdventureMapWidget::buildMapButton(const JsonNode & input)
 {
 	auto position = readTargetArea(input["area"]);
-	auto image = input["image"].String();
+	auto image = AnimationPath::fromJson(input["image"]);
 	auto help = readHintText(input["help"]);
 	bool playerColored = input["playerColored"].Bool();
 
@@ -259,9 +256,8 @@ std::shared_ptr<CIntObject> AdventureMapWidget::buildMapIcon(const JsonNode & in
 	Rect area = readTargetArea(input["area"]);
 	size_t index = input["index"].Integer();
 	size_t perPlayer = input["perPlayer"].Integer();
-	std::string image = input["image"].String();
 
-	return std::make_shared<CAdventureMapIcon>(area.topLeft(), loadAnimation(image), index, perPlayer);
+	return std::make_shared<CAdventureMapIcon>(area.topLeft(), loadAnimation(input["image"]), index, perPlayer);
 }
 
 std::shared_ptr<CIntObject> AdventureMapWidget::buildMapTownList(const JsonNode & input)
@@ -298,7 +294,7 @@ std::shared_ptr<CIntObject> AdventureMapWidget::buildMinimap(const JsonNode & in
 std::shared_ptr<CIntObject> AdventureMapWidget::buildResourceDateBar(const JsonNode & input)
 {
 	Rect area = readTargetArea(input["area"]);
-	std::string image = input["image"].String();
+	auto image = ImagePath::fromJson(input["image"]);
 
 	auto result = std::make_shared<CResDataBar>(image, area.topLeft());
 
@@ -320,7 +316,7 @@ std::shared_ptr<CIntObject> AdventureMapWidget::buildResourceDateBar(const JsonN
 std::shared_ptr<CIntObject> AdventureMapWidget::buildStatusBar(const JsonNode & input)
 {
 	Rect area = readTargetArea(input["area"]);
-	std::string image = input["image"].String();
+	auto image = ImagePath::fromJson(input["image"]);
 
 	auto background = std::make_shared<CFilledTexture>(image, area);
 
@@ -330,7 +326,7 @@ std::shared_ptr<CIntObject> AdventureMapWidget::buildStatusBar(const JsonNode &
 std::shared_ptr<CIntObject> AdventureMapWidget::buildTexturePlayerColored(const JsonNode & input)
 {
 	logGlobal->debug("Building widget CFilledTexture");
-	auto image = input["image"].String();
+	auto image = ImagePath::fromJson(input["image"]);
 	Rect area = readTargetArea(input["area"]);
 	return std::make_shared<FilledTexturePlayerColored>(image, area);
 }

+ 5 - 5
client/adventureMap/AdventureMapWidget.h

@@ -29,11 +29,11 @@ class AdventureMapWidget : public InterfaceObjectConfigurable
 	std::vector<Rect> subwidgetSizes;
 
 	/// list of images on which player-colored palette will be applied
-	std::vector<std::string> playerColorerImages;
+	std::vector<ImagePath> playerColorerImages;
 
 	/// list of named images shared between widgets
-	std::map<std::string, std::shared_ptr<IImage>> images;
-	std::map<std::string, std::shared_ptr<CAnimation>> animations;
+	std::map<ImagePath, std::shared_ptr<IImage>> images;
+	std::map<AnimationPath, std::shared_ptr<CAnimation>> animations;
 
 	/// Widgets that require access from adventure map
 	std::shared_ptr<CHeroList> heroList;
@@ -48,8 +48,8 @@ class AdventureMapWidget : public InterfaceObjectConfigurable
 	Rect readSourceArea(const JsonNode & source, const JsonNode & sourceCommon);
 	Rect readArea(const JsonNode & source, const Rect & boundingBox);
 
-	std::shared_ptr<IImage> loadImage(const std::string & name);
-	std::shared_ptr<CAnimation> loadAnimation(const std::string & name);
+	std::shared_ptr<IImage> loadImage(const JsonNode & name);
+	std::shared_ptr<CAnimation> loadAnimation(const JsonNode & name);
 
 	std::shared_ptr<CIntObject> buildInfobox(const JsonNode & input);
 	std::shared_ptr<CIntObject> buildMapImage(const JsonNode & input);

+ 6 - 6
client/adventureMap/AdventureOptions.cpp

@@ -25,22 +25,22 @@
 #include "../../lib/StartInfo.h"
 
 AdventureOptions::AdventureOptions()
-	: CWindowObject(PLAYER_COLORED, "ADVOPTS")
+	: CWindowObject(PLAYER_COLORED, ImagePath::builtin("ADVOPTS"))
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 
-	viewWorld = std::make_shared<CButton>(Point(24, 23), "ADVVIEW.DEF", CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_WORLD);
+	viewWorld = std::make_shared<CButton>(Point(24, 23), AnimationPath::builtin("ADVVIEW.DEF"), CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_WORLD);
 	viewWorld->addCallback( [] { LOCPLINT->viewWorldMap(); });
 
-	exit = std::make_shared<CButton>(Point(204, 313), "IOK6432.DEF", CButton::tooltip(), std::bind(&AdventureOptions::close, this), EShortcut::GLOBAL_RETURN);
+	exit = std::make_shared<CButton>(Point(204, 313), AnimationPath::builtin("IOK6432.DEF"), CButton::tooltip(), std::bind(&AdventureOptions::close, this), EShortcut::GLOBAL_RETURN);
 
-	scenInfo = std::make_shared<CButton>(Point(24, 198), "ADVINFO.DEF", CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_SCENARIO);
+	scenInfo = std::make_shared<CButton>(Point(24, 198), AnimationPath::builtin("ADVINFO.DEF"), CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_SCENARIO);
 	scenInfo->addCallback(AdventureOptions::showScenarioInfo);
 
-	puzzle = std::make_shared<CButton>(Point(24, 81), "ADVPUZ.DEF", CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_PUZZLE);
+	puzzle = std::make_shared<CButton>(Point(24, 81), AnimationPath::builtin("ADVPUZ.DEF"), CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_PUZZLE);
 	puzzle->addCallback(std::bind(&CPlayerInterface::showPuzzleMap, LOCPLINT));
 
-	dig = std::make_shared<CButton>(Point(24, 139), "ADVDIG.DEF", CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_DIG_GRAIL);
+	dig = std::make_shared<CButton>(Point(24, 139), AnimationPath::builtin("ADVDIG.DEF"), CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_DIG_GRAIL);
 	if(const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero())
 		dig->addCallback(std::bind(&CPlayerInterface::tryDigging, LOCPLINT, h));
 	else

+ 1 - 1
client/adventureMap/CInGameConsole.cpp

@@ -105,7 +105,7 @@ void CInGameConsole::print(const std::string & txt)
 	}
 
 	GH.windows().totalRedraw(); // FIXME: ingame console has no parent widget set
-	CCS->soundh->playSound("CHAT");
+	CCS->soundh->playSound(AudioPath::builtin("CHAT"));
 }
 
 bool CInGameConsole::captureThisKey(EShortcut key)

+ 19 - 19
client/adventureMap/CInfoBar.cpp

@@ -51,7 +51,7 @@ CInfoBar::EmptyVisibleInfo::EmptyVisibleInfo()
 CInfoBar::VisibleHeroInfo::VisibleHeroInfo(const CGHeroInstance * hero)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-	background = std::make_shared<CPicture>("ADSTATHR");
+	background = std::make_shared<CPicture>(ImagePath::builtin("ADSTATHR"));
 
 	if(settings["gameTweaks"]["infoBarCreatureManagement"].Bool())
 		heroTooltip = std::make_shared<CInteractableHeroTooltip>(Point(0,0), hero);
@@ -62,7 +62,7 @@ CInfoBar::VisibleHeroInfo::VisibleHeroInfo(const CGHeroInstance * hero)
 CInfoBar::VisibleTownInfo::VisibleTownInfo(const CGTownInstance * town)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-	background = std::make_shared<CPicture>("ADSTATCS");
+	background = std::make_shared<CPicture>(ImagePath::builtin("ADSTATCS"));
 
 	if(settings["gameTweaks"]["infoBarCreatureManagement"].Bool())
 		townTooltip = std::make_shared<CInteractableTownTooltip>(Point(0,0), town);
@@ -88,36 +88,36 @@ CInfoBar::VisibleDateInfo::VisibleDateInfo()
 	forceRefresh.push_back(label);
 }
 
-std::string CInfoBar::VisibleDateInfo::getNewDayName()
+AnimationPath CInfoBar::VisibleDateInfo::getNewDayName()
 {
 	if(LOCPLINT->cb->getDate(Date::DAY) == 1)
-		return "NEWDAY";
+		return AnimationPath::builtin("NEWDAY");
 
 	if(LOCPLINT->cb->getDate(Date::DAY_OF_WEEK) != 1)
-		return "NEWDAY";
+		return AnimationPath::builtin("NEWDAY");
 
 	switch(LOCPLINT->cb->getDate(Date::WEEK))
 	{
 	case 1:
-		return "NEWWEEK1";
+		return AnimationPath::builtin("NEWWEEK1");
 	case 2:
-		return "NEWWEEK2";
+		return AnimationPath::builtin("NEWWEEK2");
 	case 3:
-		return "NEWWEEK3";
+		return AnimationPath::builtin("NEWWEEK3");
 	case 4:
-		return "NEWWEEK4";
+		return AnimationPath::builtin("NEWWEEK4");
 	default:
-		return "";
+		return AnimationPath();
 	}
 }
 
 CInfoBar::VisibleEnemyTurnInfo::VisibleEnemyTurnInfo(PlayerColor player)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-	background = std::make_shared<CPicture>("ADSTATNX");
-	banner = std::make_shared<CAnimImage>("CREST58", player.getNum(), 0, 20, 51);
-	sand = std::make_shared<CShowableAnim>(99, 51, "HOURSAND", 0, 100); // H3 uses around 100 ms per frame
-	glass = std::make_shared<CShowableAnim>(99, 51, "HOURGLAS", CShowableAnim::PLAY_ONCE, 1000); // H3 scales this nicely for AI turn duration, don't have anything like that in vcmi
+	background = std::make_shared<CPicture>(ImagePath::builtin("ADSTATNX"));
+	banner = std::make_shared<CAnimImage>(AnimationPath::builtin("CREST58"), player.getNum(), 0, 20, 51);
+	sand = std::make_shared<CShowableAnim>(99, 51, AnimationPath::builtin("HOURSAND"), 0, 100); // H3 uses around 100 ms per frame
+	glass = std::make_shared<CShowableAnim>(99, 51, AnimationPath::builtin("HOURGLAS"), CShowableAnim::PLAY_ONCE, 1000); // H3 scales this nicely for AI turn duration, don't have anything like that in vcmi
 }
 
 CInfoBar::VisibleGameStatusInfo::VisibleGameStatusInfo()
@@ -148,14 +148,14 @@ CInfoBar::VisibleGameStatusInfo::VisibleGameStatusInfo()
 	}
 
 	//generate widgets
-	background = std::make_shared<CPicture>("ADSTATIN");
+	background = std::make_shared<CPicture>(ImagePath::builtin("ADSTATIN"));
 	allyLabel = std::make_shared<CLabel>(10, 106, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[390] + ":");
 	enemyLabel = std::make_shared<CLabel>(10, 136, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[391] + ":");
 
 	int posx = allyLabel->pos.w + allyLabel->pos.x - pos.x + 4;
 	for(PlayerColor & player : allies)
 	{
-		auto image = std::make_shared<CAnimImage>("ITGFLAGS", player.getNum(), 0, posx, 102);
+		auto image = std::make_shared<CAnimImage>(AnimationPath::builtin("ITGFLAGS"), player.getNum(), 0, posx, 102);
 		posx += image->pos.w;
 		flags.push_back(image);
 	}
@@ -163,14 +163,14 @@ CInfoBar::VisibleGameStatusInfo::VisibleGameStatusInfo()
 	posx = enemyLabel->pos.w + enemyLabel->pos.x - pos.x + 4;
 	for(PlayerColor & player : enemies)
 	{
-		auto image = std::make_shared<CAnimImage>("ITGFLAGS", player.getNum(), 0, posx, 132);
+		auto image = std::make_shared<CAnimImage>(AnimationPath::builtin("ITGFLAGS"), player.getNum(), 0, posx, 132);
 		posx += image->pos.w;
 		flags.push_back(image);
 	}
 
 	for(size_t i=0; i<halls.size(); i++)
 	{
-		hallIcons.push_back(std::make_shared<CAnimImage>("itmtl", i, 0, 6 + 42 * (int)i , 11));
+		hallIcons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("itmtl"), i, 0, 6 + 42 * (int)i , 11));
 		if(halls[i])
 			hallLabels.push_back(std::make_shared<CLabel>( 26 + 42 * (int)i, 64, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, std::to_string(halls[i])));
 	}
@@ -180,7 +180,7 @@ CInfoBar::VisibleComponentInfo::VisibleComponentInfo(const std::vector<Component
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 
-	background = std::make_shared<CPicture>("ADSTATOT", 1, 0);
+	background = std::make_shared<CPicture>(ImagePath::builtin("ADSTATOT"), 1, 0);
 	auto fullRect = Rect(CInfoBar::offset, CInfoBar::offset, data_width - 2 * CInfoBar::offset, data_height - 2 * CInfoBar::offset);
 	auto textRect = fullRect;
 	auto imageRect = fullRect;

+ 2 - 1
client/adventureMap/CInfoBar.h

@@ -11,6 +11,7 @@
 
 #include "../gui/CIntObject.h"
 #include "CConfigHandler.h"
+#include "../../lib/filesystem/ResourcePath.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -86,7 +87,7 @@ private:
 		std::shared_ptr<CShowableAnim> animation;
 		std::shared_ptr<CLabel> label;
 
-		std::string getNewDayName();
+		AnimationPath getNewDayName();
 	public:
 		VisibleDateInfo();
 	};

+ 10 - 10
client/adventureMap/CList.cpp

@@ -206,9 +206,9 @@ void CList::selectPrev()
 CHeroList::CEmptyHeroItem::CEmptyHeroItem()
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-	movement = std::make_shared<CAnimImage>("IMOBIL", 0, 0, 0, 1);
-	portrait = std::make_shared<CPicture>("HPSXXX", movement->pos.w + 1, 0);
-	mana = std::make_shared<CAnimImage>("IMANA", 0, 0, movement->pos.w + portrait->pos.w + 2, 1 );
+	movement = std::make_shared<CAnimImage>(AnimationPath::builtin("IMOBIL"), 0, 0, 0, 1);
+	portrait = std::make_shared<CPicture>(ImagePath::builtin("HPSXXX"), movement->pos.w + 1, 0);
+	mana = std::make_shared<CAnimImage>(AnimationPath::builtin("IMANA"), 0, 0, movement->pos.w + portrait->pos.w + 2, 1 );
 
 	pos.w = mana->pos.w + mana->pos.x - pos.x;
 	pos.h = std::max(std::max<int>(movement->pos.h + 1, mana->pos.h + 1), portrait->pos.h);
@@ -219,9 +219,9 @@ CHeroList::CHeroItem::CHeroItem(CHeroList *parent, const CGHeroInstance * Hero)
 	hero(Hero)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-	movement = std::make_shared<CAnimImage>("IMOBIL", 0, 0, 0, 1);
-	portrait = std::make_shared<CAnimImage>("PortraitsSmall", hero->portrait, 0, movement->pos.w + 1);
-	mana = std::make_shared<CAnimImage>("IMANA", 0, 0, movement->pos.w + portrait->pos.w + 2, 1);
+	movement = std::make_shared<CAnimImage>(AnimationPath::builtin("IMOBIL"), 0, 0, 0, 1);
+	portrait = std::make_shared<CAnimImage>(AnimationPath::builtin("PortraitsSmall"), hero->portrait, 0, movement->pos.w + 1);
+	mana = std::make_shared<CAnimImage>(AnimationPath::builtin("IMANA"), 0, 0, movement->pos.w + portrait->pos.w + 2, 1);
 
 	pos.w = mana->pos.w + mana->pos.x - pos.x;
 	pos.h = std::max(std::max<int>(movement->pos.h + 1, mana->pos.h + 1), portrait->pos.h);
@@ -238,7 +238,7 @@ void CHeroList::CHeroItem::update()
 
 std::shared_ptr<CIntObject> CHeroList::CHeroItem::genSelection()
 {
-	return std::make_shared<CPicture>("HPSYYY", movement->pos.w + 1, 0);
+	return std::make_shared<CPicture>(ImagePath::builtin("HPSYYY"), movement->pos.w + 1, 0);
 }
 
 void CHeroList::CHeroItem::select(bool on)
@@ -319,7 +319,7 @@ std::shared_ptr<CIntObject> CTownList::createItem(size_t index)
 {
 	if (LOCPLINT->localState->getOwnedTowns().size() > index)
 		return std::make_shared<CTownItem>(this, LOCPLINT->localState->getOwnedTown(index));
-	return std::make_shared<CAnimImage>("ITPA", 0);
+	return std::make_shared<CAnimImage>(AnimationPath::builtin("ITPA"), 0);
 }
 
 CTownList::CTownItem::CTownItem(CTownList *parent, const CGTownInstance *Town):
@@ -327,14 +327,14 @@ CTownList::CTownItem::CTownItem(CTownList *parent, const CGTownInstance *Town):
 	town(Town)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-	picture = std::make_shared<CAnimImage>("ITPA", 0);
+	picture = std::make_shared<CAnimImage>(AnimationPath::builtin("ITPA"), 0);
 	pos = picture->pos;
 	update();
 }
 
 std::shared_ptr<CIntObject> CTownList::CTownItem::genSelection()
 {
-	return std::make_shared<CAnimImage>("ITPA", 1);
+	return std::make_shared<CAnimImage>(AnimationPath::builtin("ITPA"), 1);
 }
 
 void CTownList::CTownItem::update()

+ 1 - 1
client/adventureMap/CMinimap.cpp

@@ -94,7 +94,7 @@ CMinimap::CMinimap(const Rect & position)
 	pos.w = position.w;
 	pos.h = position.h;
 
-	aiShield = std::make_shared<CPicture>("AIShield");
+	aiShield = std::make_shared<CPicture>(ImagePath::builtin("AIShield"));
 	aiShield->disable();
 }
 

+ 2 - 2
client/adventureMap/CResDataBar.cpp

@@ -24,7 +24,7 @@
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/ResourceSet.h"
 
-CResDataBar::CResDataBar(const std::string & imageName, const Point & position)
+CResDataBar::CResDataBar(const ImagePath & imageName, const Point & position)
 {
 	pos.x += position.x;
 	pos.y += position.y;
@@ -37,7 +37,7 @@ CResDataBar::CResDataBar(const std::string & imageName, const Point & position)
 	pos.h = background->pos.h;
 }
 
-CResDataBar::CResDataBar(const std::string & defname, int x, int y, int offx, int offy, int resdist, int datedist):
+CResDataBar::CResDataBar(const ImagePath & defname, int x, int y, int offx, int offy, int resdist, int datedist):
 	CResDataBar(defname, Point(x,y))
 {
 	for (int i = 0; i < 7 ; i++)

+ 3 - 2
client/adventureMap/CResDataBar.h

@@ -10,6 +10,7 @@
 #pragma once
 
 #include "../gui/CIntObject.h"
+#include "../../lib/filesystem/ResourcePath.h"
 
 /// Resources bar which shows information about how many gold, crystals,... you have
 /// Current date is displayed too
@@ -25,10 +26,10 @@ class CResDataBar : public CIntObject
 public:
 
 	/// For dynamically-sized UI windows, e.g. adventure map interface
-	CResDataBar(const std::string & imageName, const Point & position);
+	CResDataBar(const ImagePath & imageName, const Point & position);
 
 	/// For fixed-size UI windows, e.g. CastleInterface
-	CResDataBar(const std::string &defname, int x, int y, int offx, int offy, int resdist, int datedist);
+	CResDataBar(const ImagePath & defname, int x, int y, int offx, int offy, int resdist, int datedist);
 
 	void setDatePosition(const Point & position);
 	void setResourcePosition(const GameResID & resource, const Point & position);

+ 5 - 5
client/adventureMap/MapAudioPlayer.cpp

@@ -123,9 +123,9 @@ void MapAudioPlayer::removeObject(const CGObjectInstance * obj)
 				vstd::erase(objects[z][x][y], obj->id);
 }
 
-std::vector<std::string> MapAudioPlayer::getAmbientSounds(const int3 & tile)
+std::vector<AudioPath> MapAudioPlayer::getAmbientSounds(const int3 & tile)
 {
-	std::vector<std::string> result;
+	std::vector<AudioPath> result;
 
 	for(auto & objectID : objects[tile.z][tile.x][tile.y])
 	{
@@ -140,15 +140,15 @@ std::vector<std::string> MapAudioPlayer::getAmbientSounds(const int3 & tile)
 	}
 
 	if(CGI->mh->getMap()->isCoastalTile(tile))
-		result.emplace_back("LOOPOCEA");
+		result.emplace_back(AudioPath::builtin("LOOPOCEA"));
 
 	return result;
 }
 
 void MapAudioPlayer::updateAmbientSounds()
 {
-	std::map<std::string, int> currentSounds;
-	auto updateSounds = [&](const std::string& soundId, int distance) -> void
+	std::map<AudioPath, int> currentSounds;
+	auto updateSounds = [&](const AudioPath& soundId, int distance) -> void
 	{
 		if(vstd::contains(currentSounds, soundId))
 			currentSounds[soundId] = std::min(currentSounds[soundId], distance);

+ 2 - 1
client/adventureMap/MapAudioPlayer.h

@@ -10,6 +10,7 @@
 #pragma once
 
 #include "../mapView/IMapRendererObserver.h"
+#include "../../lib/filesystem/ResourcePath.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 class ObjectInstanceID;
@@ -29,7 +30,7 @@ class MapAudioPlayer : public IMapObjectObserver
 	void addObject(const CGObjectInstance * obj);
 	void removeObject(const CGObjectInstance * obj);
 
-	std::vector<std::string> getAmbientSounds(const int3 & tile);
+	std::vector<AudioPath> getAmbientSounds(const int3 & tile);
 	void updateAmbientSounds();
 	void updateMusic();
 	void update();

+ 3 - 3
client/adventureMap/TurnTimerWidget.cpp

@@ -25,7 +25,7 @@
 #include "../../CCallback.h"
 #include "../../lib/CStack.h"
 #include "../../lib/CPlayerState.h"
-#include "../../lib/filesystem/ResourceID.h"
+#include "../../lib/filesystem/ResourcePath.h"
 
 TurnTimerWidget::DrawRect::DrawRect(const Rect & r, const ColorRGBA & c):
 	CIntObject(), rect(r), color(c)
@@ -47,7 +47,7 @@ TurnTimerWidget::TurnTimerWidget():
 	
 	recActions &= ~DEACTIVATE;
 	
-	const JsonNode config(ResourceID("config/widgets/turnTimer.json"));
+	const JsonNode config(JsonPath::builtin("config/widgets/turnTimer.json"));
 	
 	build(config);
 	
@@ -77,7 +77,7 @@ void TurnTimerWidget::setTime(PlayerColor player, int time)
 	   && newTime != turnTime
 	   && notifications.count(newTime))
 	{
-		CCS->soundh->playSound(variables["notificationSound"].String());
+		CCS->soundh->playSound(AudioPath::fromJson(variables["notificationSound"]));
 	}
 
 	turnTime = newTime;

+ 20 - 19
client/battle/BattleAnimationClasses.cpp

@@ -24,6 +24,7 @@
 #include "../CPlayerInterface.h"
 #include "../gui/CursorHandler.h"
 #include "../gui/CGuiHandler.h"
+#include "../render/IRenderHandler.h"
 
 #include "../../CCallback.h"
 #include "../../lib/CStack.h"
@@ -114,7 +115,7 @@ void StackActionAnimation::setGroup( ECreatureAnimType group )
 	currGroup = group;
 }
 
-void StackActionAnimation::setSound( std::string sound )
+void StackActionAnimation::setSound( const AudioPath & sound )
 {
 	this->sound = sound;
 }
@@ -179,7 +180,7 @@ HittedAnimation::HittedAnimation(BattleInterface & owner, const CStack * stack)
 	: StackActionAnimation(owner, stack)
 {
 	setGroup(ECreatureAnimType::HITTED);
-	setSound(battle_sound(stack->unitType(), wince));
+	setSound(stack->unitType()->sounds.wince);
 	logAnim->debug("Created HittedAnimation for %s", stack->getName());
 }
 
@@ -187,14 +188,14 @@ DefenceAnimation::DefenceAnimation(BattleInterface & owner, const CStack * stack
 	: StackActionAnimation(owner, stack)
 {
 	setGroup(ECreatureAnimType::DEFENCE);
-	setSound(battle_sound(stack->unitType(), defend));
+	setSound(stack->unitType()->sounds.defend);
 	logAnim->debug("Created DefenceAnimation for %s", stack->getName());
 }
 
 DeathAnimation::DeathAnimation(BattleInterface & owner, const CStack * stack, bool ranged):
 	StackActionAnimation(owner, stack)
 {
-	setSound(battle_sound(stack->unitType(), killed));
+	setSound(stack->unitType()->sounds.killed);
 
 	if(ranged && myAnim->framesInGroup(ECreatureAnimType::DEATH_RANGED) > 0)
 		setGroup(ECreatureAnimType::DEATH_RANGED);
@@ -315,7 +316,7 @@ MeleeAttackAnimation::MeleeAttackAnimation(BattleInterface & owner, const CStack
 	: AttackAnimation(owner, attacker, _dest, _attacked)
 {
 	logAnim->debug("Created MeleeAttackAnimation for %s", attacker->getName());
-	setSound(battle_sound(getCreature(), attack));
+	setSound(getCreature()->sounds.attack);
 	setGroup(selectGroup(multiAttack));
 }
 
@@ -356,7 +357,7 @@ bool MovementAnimation::init()
 
 	if (moveSoundHander == -1)
 	{
-		moveSoundHander = CCS->soundh->playSound(battle_sound(stack->unitType(), move), -1);
+		moveSoundHander = CCS->soundh->playSound(stack->unitType()->sounds.move, -1);
 	}
 
 	Point begPosition = owner.stacksController->getStackPositionAtHex(prevHex, stack);
@@ -453,7 +454,7 @@ bool MovementEndAnimation::init()
 	logAnim->debug("CMovementEndAnimation::init: stack %s", stack->getName());
 	myAnim->pos.moveTo(owner.stacksController->getStackPositionAtHex(nextHex, stack));
 
-	CCS->soundh->playSound(battle_sound(stack->unitType(), endMoving));
+	CCS->soundh->playSound(stack->unitType()->sounds.endMoving);
 
 	if(!myAnim->framesInGroup(ECreatureAnimType::MOVE_END))
 	{
@@ -494,7 +495,7 @@ bool MovementStartAnimation::init()
 	}
 
 	logAnim->debug("CMovementStartAnimation::init: stack %s", stack->getName());
-	CCS->soundh->playSound(battle_sound(stack->unitType(), startMoving));
+	CCS->soundh->playSound(stack->unitType()->sounds.startMoving);
 
 	if(!myAnim->framesInGroup(ECreatureAnimType::MOVE_START))
 	{
@@ -632,7 +633,7 @@ RangedAttackAnimation::RangedAttackAnimation(BattleInterface & owner_, const CSt
 	: AttackAnimation(owner_, attacker, dest_, defender),
 	  projectileEmitted(false)
 {
-	setSound(battle_sound(getCreature(), shoot));
+	setSound(getCreature()->sounds.shoot);
 }
 
 bool RangedAttackAnimation::init()
@@ -806,8 +807,8 @@ void CatapultAnimation::tick(uint32_t msPassed)
 	explosionEmitted = true;
 	Point shotTarget = owner.stacksController->getStackPositionAtHex(dest, defendingStack) + Point(225, 225) - Point(126, 105);
 
-	std::string soundFilename  = (catapultDamage > 0) ? "WALLHIT" : "WALLMISS";
-	std::string effectFilename = (catapultDamage > 0) ? "SGEXPL" : "CSGRCK";
+	auto soundFilename  = AudioPath::builtin((catapultDamage > 0) ? "WALLHIT" : "WALLMISS");
+	AnimationPath effectFilename = AnimationPath::builtin((catapultDamage > 0) ? "SGEXPL" : "CSGRCK");
 
 	CCS->soundh->playSound( soundFilename );
 	owner.stacksController->addNewAnim( new EffectAnimation(owner, effectFilename, shotTarget));
@@ -879,42 +880,42 @@ uint32_t CastAnimation::getAttackClimaxFrame() const
 	return maxFrames / 2;
 }
 
-EffectAnimation::EffectAnimation(BattleInterface & owner, std::string animationName, int effects, bool reversed):
+EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, int effects, bool reversed):
 	BattleAnimation(owner),
-	animation(std::make_shared<CAnimation>(animationName)),
+	animation(GH.renderHandler().loadAnimation(animationName)),
 	effectFlags(effects),
 	effectFinished(false),
 	reversed(reversed)
 {
-	logAnim->debug("CPointEffectAnimation::init: effect %s", animationName);
+	logAnim->debug("CPointEffectAnimation::init: effect %s", animationName.getName());
 }
 
-EffectAnimation::EffectAnimation(BattleInterface & owner, std::string animationName, std::vector<BattleHex> hex, int effects, bool reversed):
+EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector<BattleHex> hex, int effects, bool reversed):
 	EffectAnimation(owner, animationName, effects, reversed)
 {
 	battlehexes = hex;
 }
 
-EffectAnimation::EffectAnimation(BattleInterface & owner, std::string animationName, BattleHex hex, int effects, bool reversed):
+EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, BattleHex hex, int effects, bool reversed):
 	EffectAnimation(owner, animationName, effects, reversed)
 {
 	assert(hex.isValid());
 	battlehexes.push_back(hex);
 }
 
-EffectAnimation::EffectAnimation(BattleInterface & owner, std::string animationName, std::vector<Point> pos, int effects, bool reversed):
+EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector<Point> pos, int effects, bool reversed):
 	EffectAnimation(owner, animationName, effects, reversed)
 {
 	positions = pos;
 }
 
-EffectAnimation::EffectAnimation(BattleInterface & owner, std::string animationName, Point pos, int effects, bool reversed):
+EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, Point pos, int effects, bool reversed):
 	EffectAnimation(owner, animationName, effects, reversed)
 {
 	positions.push_back(pos);
 }
 
-EffectAnimation::EffectAnimation(BattleInterface & owner, std::string animationName, Point pos, BattleHex hex, int effects, bool reversed):
+EffectAnimation::EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, Point pos, BattleHex hex, int effects, bool reversed):
 	EffectAnimation(owner, animationName, effects, reversed)
 {
 	assert(hex.isValid());

+ 9 - 8
client/battle/BattleAnimationClasses.h

@@ -10,6 +10,7 @@
 #pragma once
 
 #include "../../lib/battle/BattleHex.h"
+#include "../../lib/filesystem/ResourcePath.h"
 #include "BattleConstants.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
@@ -68,11 +69,11 @@ class StackActionAnimation : public BattleStackAnimation
 {
 	ECreatureAnimType nextGroup;
 	ECreatureAnimType currGroup;
-	std::string sound;
+	AudioPath sound;
 public:
 	void setNextGroup( ECreatureAnimType group );
 	void setGroup( ECreatureAnimType group );
-	void setSound( std::string sound );
+	void setSound( const AudioPath & sound );
 
 	ECreatureAnimType getGroup() const;
 
@@ -334,17 +335,17 @@ public:
 	};
 
 	/// Create animation with screen-wide effect
-	EffectAnimation(BattleInterface & owner, std::string animationName, int effects = 0, bool reversed = false);
+	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, int effects = 0, bool reversed = false);
 
 	/// Create animation positioned at point(s). Note that positions must be are absolute, including battleint position offset
-	EffectAnimation(BattleInterface & owner, std::string animationName, Point pos                 , int effects = 0, bool reversed = false);
-	EffectAnimation(BattleInterface & owner, std::string animationName, std::vector<Point> pos    , int effects = 0, bool reversed = false);
+	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, Point pos                 , int effects = 0, bool reversed = false);
+	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector<Point> pos    , int effects = 0, bool reversed = false);
 
 	/// Create animation positioned at certain hex(es)
-	EffectAnimation(BattleInterface & owner, std::string animationName, BattleHex hex             , int effects = 0, bool reversed = false);
-	EffectAnimation(BattleInterface & owner, std::string animationName, std::vector<BattleHex> hex, int effects = 0, bool reversed = false);
+	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, BattleHex hex             , int effects = 0, bool reversed = false);
+	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, std::vector<BattleHex> hex, int effects = 0, bool reversed = false);
 
-	EffectAnimation(BattleInterface & owner, std::string animationName, Point pos, BattleHex hex,   int effects = 0, bool reversed = false);
+	EffectAnimation(BattleInterface & owner, const AnimationPath & animationName, Point pos, BattleHex hex,   int effects = 0, bool reversed = false);
 	 ~EffectAnimation();
 
 	bool init() override;

+ 11 - 11
client/battle/BattleEffectsController.cpp

@@ -27,7 +27,7 @@
 
 #include "../../CCallback.h"
 #include "../../lib/battle/BattleAction.h"
-#include "../../lib/filesystem/ResourceID.h"
+#include "../../lib/filesystem/ResourcePath.h"
 #include "../../lib/NetPacks.h"
 #include "../../lib/CStack.h"
 #include "../../lib/IGameEventsReceiver.h"
@@ -41,14 +41,14 @@ BattleEffectsController::BattleEffectsController(BattleInterface & owner):
 
 void BattleEffectsController::displayEffect(EBattleEffect effect, const BattleHex & destTile)
 {
-	displayEffect(effect, "", destTile);
+	displayEffect(effect, AudioPath(), destTile);
 }
 
-void BattleEffectsController::displayEffect(EBattleEffect effect, std::string soundFile, const BattleHex & destTile)
+void BattleEffectsController::displayEffect(EBattleEffect effect, const AudioPath & soundFile, const BattleHex & destTile)
 {
 	size_t effectID = static_cast<size_t>(effect);
 
-	std::string customAnim = graphics->battleACToDef[effectID][0];
+	AnimationPath customAnim = AnimationPath::builtinTODO(graphics->battleACToDef[effectID][0]);
 
 	CCS->soundh->playSound( soundFile );
 
@@ -69,22 +69,22 @@ void BattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & bt
 	switch(static_cast<BonusType>(bte.effect))
 	{
 		case BonusType::HP_REGENERATION:
-			displayEffect(EBattleEffect::REGENERATION, "REGENER", stack->getPosition());
+			displayEffect(EBattleEffect::REGENERATION, AudioPath::builtin("REGENER"), stack->getPosition());
 			break;
 		case BonusType::MANA_DRAIN:
-			displayEffect(EBattleEffect::MANA_DRAIN, "MANADRAI", stack->getPosition());
+			displayEffect(EBattleEffect::MANA_DRAIN, AudioPath::builtin("MANADRAI"), stack->getPosition());
 			break;
 		case BonusType::POISON:
-			displayEffect(EBattleEffect::POISON, "POISON", stack->getPosition());
+			displayEffect(EBattleEffect::POISON, AudioPath::builtin("POISON"), stack->getPosition());
 			break;
 		case BonusType::FEAR:
-			displayEffect(EBattleEffect::FEAR, "FEAR", stack->getPosition());
+			displayEffect(EBattleEffect::FEAR, AudioPath::builtin("FEAR"), stack->getPosition());
 			break;
 		case BonusType::MORALE:
 		{
 			std::string hlp = CGI->generaltexth->allTexts[33];
 			boost::algorithm::replace_first(hlp,"%s",(stack->getName()));
-			displayEffect(EBattleEffect::GOOD_MORALE, "GOODMRLE", stack->getPosition());
+			displayEffect(EBattleEffect::GOOD_MORALE, AudioPath::builtin("GOODMRLE"), stack->getPosition());
 			owner.appendBattleLog(hlp);
 			break;
 		}
@@ -107,7 +107,7 @@ void BattleEffectsController::startAction(const BattleAction & action)
 		break;
 	case EActionType::BAD_MORALE:
 		owner.appendBattleLog(stack->formatGeneralMessage(-34));
-		displayEffect(EBattleEffect::BAD_MORALE, "BADMRLE", stack->getPosition());
+		displayEffect(EBattleEffect::BAD_MORALE, AudioPath::builtin("BADMRLE"), stack->getPosition());
 		break;
 	}
 
@@ -132,7 +132,7 @@ void BattleEffectsController::collectRenderableObjects(BattleRenderer & renderer
 
 void BattleEffectsController::loadColorMuxers()
 {
-	const JsonNode config(ResourceID("config/battleEffects.json"));
+	const JsonNode config(JsonPath::builtin("config/battleEffects.json"));
 
 	for(auto & muxer : config["colorMuxers"].Struct())
 	{

+ 2 - 1
client/battle/BattleEffectsController.h

@@ -11,6 +11,7 @@
 
 #include "../../lib/battle/BattleHex.h"
 #include "../../lib/Point.h"
+#include "../../lib/filesystem/ResourcePath.h"
 #include "BattleConstants.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
@@ -64,7 +65,7 @@ public:
 
 	//displays custom effect on the battlefield
 	void displayEffect(EBattleEffect effect, const BattleHex & destTile);
-	void displayEffect(EBattleEffect effect, std::string soundFile, const BattleHex & destTile);
+	void displayEffect(EBattleEffect effect, const AudioPath & soundFile, const BattleHex & destTile);
 
 	void battleTriggerEffect(const BattleTriggerEffect & bte);
 

+ 11 - 10
client/battle/BattleFieldController.cpp

@@ -26,6 +26,7 @@
 #include "../render/Canvas.h"
 #include "../render/IImage.h"
 #include "../renderSDL/SDL_Extensions.h"
+#include "../render/IRenderHandler.h"
 #include "../gui/CGuiHandler.h"
 #include "../gui/CursorHandler.h"
 #include "../adventureMap/CInGameConsole.h"
@@ -120,20 +121,20 @@ BattleFieldController::BattleFieldController(BattleInterface & owner):
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
 
 	//preparing cells and hexes
-	cellBorder = IImage::createFromFile("CCELLGRD.BMP", EImageBlitMode::COLORKEY);
-	cellShade = IImage::createFromFile("CCELLSHD.BMP");
-	cellUnitMovementHighlight = IImage::createFromFile("UnitMovementHighlight.PNG", EImageBlitMode::COLORKEY);
-	cellUnitMaxMovementHighlight = IImage::createFromFile("UnitMaxMovementHighlight.PNG", EImageBlitMode::COLORKEY);
+	cellBorder = GH.renderHandler().loadImage(ImagePath::builtin("CCELLGRD.BMP"), EImageBlitMode::COLORKEY);
+	cellShade = GH.renderHandler().loadImage(ImagePath::builtin("CCELLSHD.BMP"));
+	cellUnitMovementHighlight = GH.renderHandler().loadImage(ImagePath::builtin("UnitMovementHighlight.PNG"), EImageBlitMode::COLORKEY);
+	cellUnitMaxMovementHighlight = GH.renderHandler().loadImage(ImagePath::builtin("UnitMaxMovementHighlight.PNG"), EImageBlitMode::COLORKEY);
 
-	attackCursors = std::make_shared<CAnimation>("CRCOMBAT");
+	attackCursors = GH.renderHandler().loadAnimation(AnimationPath::builtin("CRCOMBAT"));
 	attackCursors->preload();
 
 	initializeHexEdgeMaskToFrameIndex();
 
-	rangedFullDamageLimitImages = std::make_shared<CAnimation>("battle/rangeHighlights/rangeHighlightsGreen.json");
+	rangedFullDamageLimitImages = GH.renderHandler().loadAnimation(AnimationPath::builtin("battle/rangeHighlights/rangeHighlightsGreen.json"));
 	rangedFullDamageLimitImages->preload();
 
-	shootingRangeLimitImages = std::make_shared<CAnimation>("battle/rangeHighlights/rangeHighlightsRed.json");
+	shootingRangeLimitImages = GH.renderHandler().loadAnimation(AnimationPath::builtin("battle/rangeHighlights/rangeHighlightsRed.json"));
 	shootingRangeLimitImages->preload();
 
 	flipRangeLimitImagesIntoPositions(rangedFullDamageLimitImages);
@@ -146,12 +147,12 @@ BattleFieldController::BattleFieldController(BattleInterface & owner):
 		if(bfieldType == BattleField::NONE)
 			logGlobal->error("Invalid battlefield returned for current battle");
 		else
-			background = IImage::createFromFile(bfieldType.getInfo()->graphics, EImageBlitMode::OPAQUE);
+			background = GH.renderHandler().loadImage(bfieldType.getInfo()->graphics, EImageBlitMode::OPAQUE);
 	}
 	else
 	{
-		std::string backgroundName = owner.siegeController->getBattleBackgroundName();
-		background = IImage::createFromFile(backgroundName, EImageBlitMode::OPAQUE);
+		auto backgroundName = owner.siegeController->getBattleBackgroundName();
+		background = GH.renderHandler().loadImage(backgroundName, EImageBlitMode::OPAQUE);
 	}
 
 	pos.w = background->width();

+ 4 - 4
client/battle/BattleInterface.cpp

@@ -354,7 +354,7 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
 	if(!spell)
 		return;
 
-	const std::string & castSoundPath = spell->getCastSound();
+	const AudioPath & castSoundPath = spell->getCastSound();
 
 	if (!castSoundPath.empty())
 	{
@@ -420,7 +420,7 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
 	if (!sc->resistedCres.empty())
 	{
 		addToAnimationStage(EAnimationEvents::HIT, [=](){
-			CCS->soundh->playSound("MAGICRES");
+			CCS->soundh->playSound(AudioPath::builtin("MAGICRES"));
 		});
 	}
 
@@ -441,8 +441,8 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
 		bool side = sc->side;
 
 		addToAnimationStage(EAnimationEvents::AFTER_HIT, [=](){
-			stacksController->addNewAnim(new EffectAnimation(*this, side ? "SP07_A.DEF" : "SP07_B.DEF", leftHero));
-			stacksController->addNewAnim(new EffectAnimation(*this, side ? "SP07_B.DEF" : "SP07_A.DEF", rightHero));
+			stacksController->addNewAnim(new EffectAnimation(*this, AnimationPath::builtin(side ? "SP07_A.DEF" : "SP07_B.DEF"), leftHero));
+			stacksController->addNewAnim(new EffectAnimation(*this, AnimationPath::builtin(side ? "SP07_B.DEF" : "SP07_A.DEF"), rightHero));
 		});
 	}
 

+ 31 - 30
client/battle/BattleInterfaceClasses.cpp

@@ -37,6 +37,7 @@
 #include "../windows/CMessage.h"
 #include "../windows/CSpellWindow.h"
 #include "../render/CAnimation.h"
+#include "../render/IRenderHandler.h"
 #include "../adventureMap/CInGameConsole.h"
 
 #include "../../CCallback.h"
@@ -342,7 +343,7 @@ BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * her
 	currentFrame(0.f),
 	flagCurrentFrame(0.f)
 {
-	std::string animationPath;
+	AnimationPath animationPath;
 
 	if(!hero->type->battleImage.empty())
 		animationPath = hero->type->battleImage;
@@ -352,7 +353,7 @@ BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * her
 	else
 		animationPath = hero->type->heroClass->imageBattleMale;
 
-	animation = std::make_shared<CAnimation>(animationPath);
+	animation = GH.renderHandler().loadAnimation(animationPath);
 	animation->preload();
 
 	pos.w = 64;
@@ -364,9 +365,9 @@ BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * her
 		animation->verticalFlip();
 
 	if(defender)
-		flagAnimation = std::make_shared<CAnimation>("CMFLAGR");
+		flagAnimation = GH.renderHandler().loadAnimation(AnimationPath::builtin("CMFLAGR"));
 	else
-		flagAnimation = std::make_shared<CAnimation>("CMFLAGL");
+		flagAnimation = GH.renderHandler().loadAnimation(AnimationPath::builtin("CMFLAGL"));
 
 	flagAnimation->preload();
 	flagAnimation->playerColored(hero->tempOwner);
@@ -386,7 +387,7 @@ HeroInfoBasicPanel::HeroInfoBasicPanel(const InfoAboutHero & hero, Point * posit
 
 	if(initializeBackground)
 	{
-		background = std::make_shared<CPicture>("CHRPOP");
+		background = std::make_shared<CPicture>(ImagePath::builtin("CHRPOP"));
 		background->getSurface()->setBlitMode(EImageBlitMode::OPAQUE);
 		background->colorize(hero.owner);
 	}
@@ -406,7 +407,7 @@ void HeroInfoBasicPanel::initializeData(const InfoAboutHero & hero)
 	auto currentSpellPoints = hero.details->mana;
 	auto maxSpellPoints = hero.details->manaLimit;
 
-	icons.push_back(std::make_shared<CAnimImage>("PortraitsLarge", hero.portrait, 0, 10, 6));
+	icons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("PortraitsLarge"), hero.portrait, 0, 10, 6));
 
 	//primary stats
 	labels.push_back(std::make_shared<CLabel>(9, 75, EFonts::FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[380] + ":"));
@@ -423,8 +424,8 @@ void HeroInfoBasicPanel::initializeData(const InfoAboutHero & hero)
 	labels.push_back(std::make_shared<CLabel>(9, 131, EFonts::FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[384] + ":"));
 	labels.push_back(std::make_shared<CLabel>(9, 143, EFonts::FONT_TINY, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[385] + ":"));
 
-	icons.push_back(std::make_shared<CAnimImage>("IMRL22", morale + 3, 0, 47, 131));
-	icons.push_back(std::make_shared<CAnimImage>("ILCK22", luck + 3, 0, 47, 143));
+	icons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("IMRL22"), morale + 3, 0, 47, 131));
+	icons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("ILCK22"), luck + 3, 0, 47, 143));
 
 	//spell points
 	labels.push_back(std::make_shared<CLabel>(39, 174, EFonts::FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[387]));
@@ -446,7 +447,7 @@ void HeroInfoBasicPanel::show(Canvas & to)
 }
 
 HeroInfoWindow::HeroInfoWindow(const InfoAboutHero & hero, Point * position)
-	: CWindowObject(RCLICK_POPUP | SHADOW_DISABLED, "CHRPOP")
+	: CWindowObject(RCLICK_POPUP | SHADOW_DISABLED, ImagePath::builtin("CHRPOP"))
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 	if (position != nullptr)
@@ -462,16 +463,16 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
 
-	background = std::make_shared<CPicture>("CPRESULT");
+	background = std::make_shared<CPicture>(ImagePath::builtin("CPRESULT"));
 	background->colorize(owner.playerID);
 	pos = center(background->pos);
 
-	exit = std::make_shared<CButton>(Point(384, 505), "iok6432.def", std::make_pair("", ""), [&](){ bExitf();}, EShortcut::GLOBAL_ACCEPT);
+	exit = std::make_shared<CButton>(Point(384, 505), AnimationPath::builtin("iok6432.def"), std::make_pair("", ""), [&](){ bExitf();}, EShortcut::GLOBAL_ACCEPT);
 	exit->setBorderColor(Colors::METALLIC_GOLD);
 	
 	if(allowReplay)
 	{
-		repeat = std::make_shared<CButton>(Point(24, 505), "icn6432.def", std::make_pair("", ""), [&](){ bRepeatf();}, EShortcut::GLOBAL_CANCEL);
+		repeat = std::make_shared<CButton>(Point(24, 505), AnimationPath::builtin("icn6432.def"), std::make_pair("", ""), [&](){ bRepeatf();}, EShortcut::GLOBAL_CANCEL);
 		repeat->setBorderColor(Colors::METALLIC_GOLD);
 		labels.push_back(std::make_shared<CLabel>(232, 520, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->translate("vcmi.battleResultsWindow.applyResultsLabel")));
 	}
@@ -507,7 +508,7 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
 
 		if(heroInfo.portrait >= 0) //attacking hero
 		{
-			icons.push_back(std::make_shared<CAnimImage>("PortraitsLarge", heroInfo.portrait, 0, xs[i], 38));
+			icons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("PortraitsLarge"), heroInfo.portrait, 0, xs[i], 38));
 			sideNames[i] = heroInfo.name;
 		}
 		else
@@ -525,7 +526,7 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
 
 			if(best != stacks.end()) //should be always but to be safe...
 			{
-				icons.push_back(std::make_shared<CAnimImage>("TWCRPORT", (*best)->unitType()->getIconIndex(), 0, xs[i], 38));
+				icons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("TWCRPORT"), (*best)->unitType()->getIconIndex(), 0, xs[i], 38));
 				sideNames[i] = (*best)->unitType()->getNamePluralTranslated();
 			}
 		}
@@ -552,7 +553,7 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
 				if (creature->getId() == CreatureID::ARROW_TOWERS )
 					continue; // do not show destroyed towers in battle results
 
-				icons.push_back(std::make_shared<CAnimImage>("CPRSMALL", creature->getIconIndex(), 0, xPos, yPos));
+				icons.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("CPRSMALL"), creature->getIconIndex(), 0, xPos, yPos));
 				std::ostringstream amount;
 				amount<<elem.second;
 				labels.push_back(std::make_shared<CLabel>(xPos + 16, yPos + 42, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, amount.str()));
@@ -580,8 +581,8 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
 			break;
 		}
 
-		CCS->musich->playMusic("Music/Win Battle", false, true);
-		CCS->videoh->open("WIN3.BIK");
+		CCS->musich->playMusic(AudioPath::builtin("Music/Win Battle"), false, true);
+		CCS->videoh->open(VideoPath::builtin("WIN3.BIK"));
 		std::string str = CGI->generaltexth->allTexts[text];
 
 		const CGHeroInstance * ourHero = owner.cb->getBattle(br.battleID)->battleGetMyHero();
@@ -597,20 +598,20 @@ BattleResultWindow::BattleResultWindow(const BattleResult & br, CPlayerInterface
 	else // we lose
 	{
 		int text = 311;
-		std::string musicName = "Music/LoseCombat";
-		std::string videoName = "LBSTART.BIK";
+		AudioPath musicName = AudioPath::builtin("Music/LoseCombat");
+		VideoPath videoName = VideoPath::builtin("LBSTART.BIK");
 		switch(br.result)
 		{
 		case EBattleResult::NORMAL:
 			break;
 		case EBattleResult::ESCAPE:
-			musicName = "Music/Retreat Battle";
-			videoName = "RTSTART.BIK";
+			musicName = AudioPath::builtin("Music/Retreat Battle");
+			videoName = VideoPath::builtin("RTSTART.BIK");
 			text = 310;
 			break;
 		case EBattleResult::SURRENDER:
-			musicName = "Music/Surrender Battle";
-			videoName = "SURRENDER.BIK";
+			musicName = AudioPath::builtin("Music/Surrender Battle");
+			videoName = VideoPath::builtin("SURRENDER.BIK");
 			text = 309;
 			break;
 		default:
@@ -676,8 +677,8 @@ StackQueue::StackQueue(bool Embedded, BattleInterface & owner)
 		pos.x += parent->pos.w/2 - pos.w/2;
 		pos.y += 10;
 
-		icons = std::make_shared<CAnimation>("CPRSMALL");
-		stateIcons = std::make_shared<CAnimation>("VCMI/BATTLEQUEUE/STATESSMALL");
+		icons = GH.renderHandler().loadAnimation(AnimationPath::builtin("CPRSMALL"));
+		stateIcons = GH.renderHandler().loadAnimation(AnimationPath::builtin("VCMI/BATTLEQUEUE/STATESSMALL"));
 	}
 	else
 	{
@@ -686,12 +687,12 @@ StackQueue::StackQueue(bool Embedded, BattleInterface & owner)
 		pos.x += 0;
 		pos.y -= pos.h;
 
-		background = std::make_shared<CFilledTexture>("DIBOXBCK", Rect(0, 0, pos.w, pos.h));
+		background = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), Rect(0, 0, pos.w, pos.h));
 
-		icons = std::make_shared<CAnimation>("TWCRPORT");
-		stateIcons = std::make_shared<CAnimation>("VCMI/BATTLEQUEUE/STATESSMALL");
+		icons = GH.renderHandler().loadAnimation(AnimationPath::builtin("TWCRPORT"));
+		stateIcons = GH.renderHandler().loadAnimation(AnimationPath::builtin("VCMI/BATTLEQUEUE/STATESSMALL"));
 		//TODO: where use big icons?
-		//stateIcons = std::make_shared<CAnimation>("VCMI/BATTLEQUEUE/STATESBIG");
+		//stateIcons = GH.renderHandler().loadAnimation("VCMI/BATTLEQUEUE/STATESBIG");
 	}
 	stateIcons->preload();
 
@@ -750,7 +751,7 @@ StackQueue::StackBox::StackBox(StackQueue * owner):
 	CIntObject(SHOW_POPUP | HOVER), owner(owner)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-	background = std::make_shared<CPicture>(owner->embedded ? "StackQueueSmall" : "StackQueueLarge");
+	background = std::make_shared<CPicture>(ImagePath::builtin(owner->embedded ? "StackQueueSmall" : "StackQueueLarge"));
 
 	pos.w = background->pos.w;
 	pos.h = background->pos.h;

+ 8 - 7
client/battle/BattleObstacleController.cpp

@@ -22,6 +22,7 @@
 #include "../CPlayerInterface.h"
 #include "../gui/CGuiHandler.h"
 #include "../render/Canvas.h"
+#include "../render/IRenderHandler.h"
 
 #include "../../CCallback.h"
 #include "../../lib/battle/CObstacleInstance.h"
@@ -42,21 +43,21 @@ BattleObstacleController::BattleObstacleController(BattleInterface & owner):
 
 void BattleObstacleController::loadObstacleImage(const CObstacleInstance & oi)
 {
-	std::string animationName = oi.getAnimation();
+	AnimationPath animationName = oi.getAnimation();
 
 	if (animationsCache.count(animationName) == 0)
 	{
 		if (oi.obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE)
 		{
 			// obstacle uses single bitmap image for animations
-			auto animation = std::make_shared<CAnimation>();
-			animation->setCustom(animationName, 0, 0);
+			auto animation = GH.renderHandler().createAnimation();
+			animation->setCustom(animationName.getName(), 0, 0);
 			animationsCache[animationName] = animation;
 			animation->preload();
 		}
 		else
 		{
-			auto animation = std::make_shared<CAnimation>(animationName);
+			auto animation = GH.renderHandler().loadAnimation(animationName);
 			animationsCache[animationName] = animation;
 			animation->preload();
 		}
@@ -76,7 +77,7 @@ void BattleObstacleController::obstacleRemoved(const std::vector<ObstacleChanges
 			continue;
 		}
 
-		auto animation = std::make_shared<CAnimation>(obstacle["appearAnimation"].String());
+		auto animation = GH.renderHandler().loadAnimation(AnimationPath::fromJson(obstacle["appearAnimation"]));
 		animation->preload();
 
 		auto first = animation->getImage(0, 0);
@@ -87,7 +88,7 @@ void BattleObstacleController::obstacleRemoved(const std::vector<ObstacleChanges
 		// -> if we know how to blit obstacle, let's blit the effect in the same place
 		Point whereTo = getObstaclePosition(first, obstacle);
 		//AFAIK, in H3 there is no sound of obstacle removal
-		owner.stacksController->addNewAnim(new EffectAnimation(owner, obstacle["appearAnimation"].String(), whereTo, obstacle["position"].Integer(), 0, true));
+		owner.stacksController->addNewAnim(new EffectAnimation(owner, AnimationPath::fromJson(obstacle["appearAnimation"]), whereTo, obstacle["position"].Integer(), 0, true));
 
 		obstacleAnimations.erase(oi.id);
 		//so when multiple obstacles are removed, they show up one after another
@@ -104,7 +105,7 @@ void BattleObstacleController::obstaclePlaced(const std::vector<std::shared_ptr<
 		if(!oi->visibleForSide(side.value(), owner.getBattle()->battleHasNativeStack(side.value())))
 			continue;
 
-		auto animation = std::make_shared<CAnimation>(oi->getAppearAnimation());
+		auto animation = GH.renderHandler().loadAnimation(oi->getAppearAnimation());
 		animation->preload();
 
 		auto first = animation->getImage(0, 0);

+ 3 - 1
client/battle/BattleObstacleController.h

@@ -9,6 +9,8 @@
  */
 #pragma once
 
+#include "../../lib/filesystem/ResourcePath.h"
+
 VCMI_LIB_NAMESPACE_BEGIN
 
 struct BattleHex;
@@ -35,7 +37,7 @@ class BattleObstacleController
 	float timePassed;
 
 	/// cached animations of all obstacles in current battle
-	std::map<std::string, std::shared_ptr<CAnimation>> animationsCache;
+	std::map<AnimationPath, std::shared_ptr<CAnimation>> animationsCache;
 
 	/// list of all obstacles that are currently being rendered
 	std::map<si32, std::shared_ptr<CAnimation>> obstacleAnimations;

+ 5 - 4
client/battle/BattleProjectileController.cpp

@@ -16,6 +16,7 @@
 #include "CreatureAnimation.h"
 
 #include "../render/Canvas.h"
+#include "../render/IRenderHandler.h"
 #include "../gui/CGuiHandler.h"
 #include "../CGameInfo.h"
 
@@ -188,9 +189,9 @@ void BattleProjectileController::initStackProjectile(const CStack * stack)
 	projectilesCache[creature.animation.projectileImageName] = createProjectileImage(creature.animation.projectileImageName);
 }
 
-std::shared_ptr<CAnimation> BattleProjectileController::createProjectileImage(const std::string & path )
+std::shared_ptr<CAnimation> BattleProjectileController::createProjectileImage(const AnimationPath & path )
 {
-	std::shared_ptr<CAnimation> projectile = std::make_shared<CAnimation>(path);
+	std::shared_ptr<CAnimation> projectile = GH.renderHandler().loadAnimation(path);
 	projectile->preload();
 
 	if(projectile->size(1) != 0)
@@ -204,7 +205,7 @@ std::shared_ptr<CAnimation> BattleProjectileController::createProjectileImage(co
 std::shared_ptr<CAnimation> BattleProjectileController::getProjectileImage(const CStack * stack)
 {
 	const CCreature & creature = getShooter(stack);
-	std::string imageName = creature.animation.projectileImageName;
+	AnimationPath imageName = creature.animation.projectileImageName;
 
 	if (!projectilesCache.count(imageName))
 		initStackProjectile(stack);
@@ -361,7 +362,7 @@ void BattleProjectileController::createProjectile(const CStack * shooter, Point
 void BattleProjectileController::createSpellProjectile(const CStack * shooter, Point from, Point dest, const CSpell * spell)
 {
 	double projectileAngle = std::abs(atan2(dest.x - from.x, dest.y - from.y));
-	std::string animToDisplay = spell->animationInfo.selectProjectile(projectileAngle);
+	AnimationPath animToDisplay = spell->animationInfo.selectProjectile(projectileAngle);
 
 	assert(!animToDisplay.empty());
 

+ 3 - 2
client/battle/BattleProjectileController.h

@@ -11,6 +11,7 @@
 
 #include "../../lib/CCreatureHandler.h"
 #include "../../lib/Point.h"
+#include "../../lib/filesystem/ResourcePath.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -83,13 +84,13 @@ class BattleProjectileController
 	BattleInterface & owner;
 
 	/// all projectiles loaded during current battle
-	std::map<std::string, std::shared_ptr<CAnimation>> projectilesCache;
+	std::map<AnimationPath, std::shared_ptr<CAnimation>> projectilesCache;
 
 	/// projectiles currently flying on battlefield
 	std::vector<std::shared_ptr<ProjectileBase>> projectiles;
 
 	std::shared_ptr<CAnimation> getProjectileImage(const CStack * stack);
-	std::shared_ptr<CAnimation> createProjectileImage(const std::string & path );
+	std::shared_ptr<CAnimation> createProjectileImage(const AnimationPath & path );
 	void initStackProjectile(const CStack * stack);
 
 	bool stackUsesRayProjectile(const CStack * stack) const;

+ 29 - 27
client/battle/BattleSiegeController.cpp

@@ -20,15 +20,17 @@
 #include "../CMusicHandler.h"
 #include "../CGameInfo.h"
 #include "../CPlayerInterface.h"
+#include "../gui/CGuiHandler.h"
 #include "../render/Canvas.h"
 #include "../render/IImage.h"
+#include "../render/IRenderHandler.h"
 
 #include "../../CCallback.h"
 #include "../../lib/NetPacks.h"
 #include "../../lib/CStack.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
 
-std::string BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisual what, EWallState state) const
+ImagePath BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisual what, EWallState state) const
 {
 	auto getImageIndex = [&]() -> int
 	{
@@ -68,44 +70,44 @@ std::string BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisua
 			auto faction = town->town->faction->getIndex();
 
 			if (faction == ETownType::RAMPART || faction == ETownType::NECROPOLIS || faction == ETownType::DUNGEON || faction == ETownType::STRONGHOLD)
-				return prefix + "TPW1.BMP";
+				return ImagePath::builtinTODO(prefix + "TPW1.BMP");
 			else
-				return prefix + "TPWL.BMP";
+				return ImagePath::builtinTODO(prefix + "TPWL.BMP");
 		}
 	case EWallVisual::KEEP:
-		return prefix + "MAN" + addit + ".BMP";
+		return ImagePath::builtinTODO(prefix + "MAN" + addit + ".BMP");
 	case EWallVisual::BOTTOM_TOWER:
-		return prefix + "TW1" + addit + ".BMP";
+		return ImagePath::builtinTODO(prefix + "TW1" + addit + ".BMP");
 	case EWallVisual::BOTTOM_WALL:
-		return prefix + "WA1" + addit + ".BMP";
+		return ImagePath::builtinTODO(prefix + "WA1" + addit + ".BMP");
 	case EWallVisual::WALL_BELLOW_GATE:
-		return prefix + "WA3" + addit + ".BMP";
+		return ImagePath::builtinTODO(prefix + "WA3" + addit + ".BMP");
 	case EWallVisual::WALL_OVER_GATE:
-		return prefix + "WA4" + addit + ".BMP";
+		return ImagePath::builtinTODO(prefix + "WA4" + addit + ".BMP");
 	case EWallVisual::UPPER_WALL:
-		return prefix + "WA6" + addit + ".BMP";
+		return ImagePath::builtinTODO(prefix + "WA6" + addit + ".BMP");
 	case EWallVisual::UPPER_TOWER:
-		return prefix + "TW2" + addit + ".BMP";
+		return ImagePath::builtinTODO(prefix + "TW2" + addit + ".BMP");
 	case EWallVisual::GATE:
-		return prefix + "DRW" + addit + ".BMP";
+		return ImagePath::builtinTODO(prefix + "DRW" + addit + ".BMP");
 	case EWallVisual::GATE_ARCH:
-		return prefix + "ARCH.BMP";
+		return ImagePath::builtinTODO(prefix + "ARCH.BMP");
 	case EWallVisual::BOTTOM_STATIC_WALL:
-		return prefix + "WA2.BMP";
+		return ImagePath::builtinTODO(prefix + "WA2.BMP");
 	case EWallVisual::UPPER_STATIC_WALL:
-		return prefix + "WA5.BMP";
+		return ImagePath::builtinTODO(prefix + "WA5.BMP");
 	case EWallVisual::MOAT:
-		return prefix + "MOAT.BMP";
+		return ImagePath::builtinTODO(prefix + "MOAT.BMP");
 	case EWallVisual::MOAT_BANK:
-		return prefix + "MLIP.BMP";
+		return ImagePath::builtinTODO(prefix + "MLIP.BMP");
 	case EWallVisual::KEEP_BATTLEMENT:
-		return prefix + "MANC.BMP";
+		return ImagePath::builtinTODO(prefix + "MANC.BMP");
 	case EWallVisual::BOTTOM_BATTLEMENT:
-		return prefix + "TW1C.BMP";
+		return ImagePath::builtinTODO(prefix + "TW1C.BMP");
 	case EWallVisual::UPPER_BATTLEMENT:
-		return prefix + "TW2C.BMP";
+		return ImagePath::builtinTODO(prefix + "TW2C.BMP");
 	default:
-		return "";
+		return ImagePath();
 	}
 }
 
@@ -118,10 +120,10 @@ void BattleSiegeController::showWallPiece(Canvas & canvas, EWallVisual::EWallVis
 		canvas.draw(wallPieceImages[what], Point(pos.x, pos.y));
 }
 
-std::string BattleSiegeController::getBattleBackgroundName() const
+ImagePath BattleSiegeController::getBattleBackgroundName() const
 {
 	const std::string & prefix = town->town->clientInfo.siegePrefix;
-	return prefix + "BACK.BMP";
+	return ImagePath::builtinTODO(prefix + "BACK.BMP");
 }
 
 bool BattleSiegeController::getWallPieceExistance(EWallVisual::EWallVisual what) const
@@ -180,7 +182,7 @@ BattleSiegeController::BattleSiegeController(BattleInterface & owner, const CGTo
 		if ( !getWallPieceExistance(EWallVisual::EWallVisual(g)) )
 			continue;
 
-		wallPieceImages[g] = IImage::createFromFile(getWallPieceImageName(EWallVisual::EWallVisual(g), EWallState::REINFORCED));
+		wallPieceImages[g] = GH.renderHandler().loadImage(getWallPieceImageName(EWallVisual::EWallVisual(g), EWallState::REINFORCED));
 	}
 }
 
@@ -246,7 +248,7 @@ void BattleSiegeController::gateStateChanged(const EGateState state)
 		wallPieceImages[EWallVisual::GATE] = nullptr;
 
 	if (stateId != EWallState::NONE)
-		wallPieceImages[EWallVisual::GATE] = IImage::createFromFile(getWallPieceImageName(EWallVisual::GATE,  stateId));
+		wallPieceImages[EWallVisual::GATE] = GH.renderHandler().loadImage(getWallPieceImageName(EWallVisual::GATE,  stateId));
 
 	if (playSound)
 		CCS->soundh->playSound(soundBase::DRAWBRG);
@@ -340,8 +342,8 @@ void BattleSiegeController::stackIsCatapulting(const CatapultAttack & ca)
 		for (auto attackInfo : ca.attackedParts)
 			positions.push_back(owner.stacksController->getStackPositionAtHex(attackInfo.destinationTile, nullptr) + Point(99, 120));
 
-		CCS->soundh->playSound( "WALLHIT" );
-		owner.stacksController->addNewAnim(new EffectAnimation(owner, "SGEXPL.DEF", positions));
+		CCS->soundh->playSound( AudioPath::builtin("WALLHIT") );
+		owner.stacksController->addNewAnim(new EffectAnimation(owner, AnimationPath::builtin("SGEXPL.DEF"), positions));
 	}
 
 	owner.waitForAnimations();
@@ -355,7 +357,7 @@ void BattleSiegeController::stackIsCatapulting(const CatapultAttack & ca)
 
 		auto wallState = EWallState(owner.getBattle()->battleGetWallState(attackInfo.attackedPart));
 
-		wallPieceImages[wallId] = IImage::createFromFile(getWallPieceImageName(EWallVisual::EWallVisual(wallId), wallState));
+		wallPieceImages[wallId] = GH.renderHandler().loadImage(getWallPieceImageName(EWallVisual::EWallVisual(wallId), wallState));
 	}
 }
 

+ 3 - 2
client/battle/BattleSiegeController.h

@@ -11,6 +11,7 @@
 
 #include "../../lib/GameConstants.h"
 #include "../../lib/battle/BattleHex.h"
+#include "../../lib/filesystem/ResourcePath.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -76,7 +77,7 @@ class BattleSiegeController
 	std::array<std::shared_ptr<IImage>, EWallVisual::WALL_LAST + 1> wallPieceImages;
 
 	/// return URI for image for a wall piece
-	std::string getWallPieceImageName(EWallVisual::EWallVisual what, EWallState state) const;
+	ImagePath getWallPieceImageName(EWallVisual::EWallVisual what, EWallState state) const;
 
 	/// returns BattleHex to which chosen wall piece is bound
 	BattleHex getWallPiecePosition(EWallVisual::EWallVisual what) const;
@@ -102,7 +103,7 @@ public:
 
 	/// queries from other battle controllers
 	bool isAttackableByCatapult(BattleHex hex) const;
-	std::string getBattleBackgroundName() const;
+	ImagePath getBattleBackgroundName() const;
 	const CCreature *getTurretCreature() const;
 	Point getTurretCreaturePosition( BattleHex position ) const;
 

+ 11 - 10
client/battle/BattleStacksController.cpp

@@ -28,6 +28,7 @@
 #include "../gui/CGuiHandler.h"
 #include "../render/Colors.h"
 #include "../render/Canvas.h"
+#include "../render/IRenderHandler.h"
 
 #include "../../CCallback.h"
 #include "../../lib/spells/ISpellMechanics.h"
@@ -76,10 +77,10 @@ BattleStacksController::BattleStacksController(BattleInterface & owner):
 	animIDhelper(0)
 {
 	//preparing graphics for displaying amounts of creatures
-	amountNormal     = IImage::createFromFile("CMNUMWIN.BMP", EImageBlitMode::COLORKEY);
-	amountPositive   = IImage::createFromFile("CMNUMWIN.BMP", EImageBlitMode::COLORKEY);
-	amountNegative   = IImage::createFromFile("CMNUMWIN.BMP", EImageBlitMode::COLORKEY);
-	amountEffNeutral = IImage::createFromFile("CMNUMWIN.BMP", EImageBlitMode::COLORKEY);
+	amountNormal     = GH.renderHandler().loadImage(ImagePath::builtin("CMNUMWIN.BMP"), EImageBlitMode::COLORKEY);
+	amountPositive   = GH.renderHandler().loadImage(ImagePath::builtin("CMNUMWIN.BMP"), EImageBlitMode::COLORKEY);
+	amountNegative   = GH.renderHandler().loadImage(ImagePath::builtin("CMNUMWIN.BMP"), EImageBlitMode::COLORKEY);
+	amountEffNeutral = GH.renderHandler().loadImage(ImagePath::builtin("CMNUMWIN.BMP"), EImageBlitMode::COLORKEY);
 
 	static const auto shifterNormal   = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 0.6f, 0.2f, 1.0f );
 	static const auto shifterPositive = ColorFilter::genRangeShifter( 0.f, 0.f, 0.f, 0.2f, 1.0f, 0.2f );
@@ -462,7 +463,7 @@ void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> at
 				addNewAnim(new HittedAnimation(owner, attackedInfo.defender));
 
 			if (attackedInfo.fireShield)
-				owner.effectsController->displayEffect(EBattleEffect::FIRE_SHIELD, "FIRESHIE", attackedInfo.attacker->getPosition());
+				owner.effectsController->displayEffect(EBattleEffect::FIRE_SHIELD, AudioPath::builtin("FIRESHIE"), attackedInfo.attacker->getPosition());
 
 			if (attackedInfo.spellEffect != SpellID::NONE)
 			{
@@ -481,7 +482,7 @@ void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> at
 		if (attackedInfo.rebirth)
 		{
 			owner.addToAnimationStage(EAnimationEvents::AFTER_HIT, [=](){
-				owner.effectsController->displayEffect(EBattleEffect::RESURRECT, "RESURECT", attackedInfo.defender->getPosition());
+				owner.effectsController->displayEffect(EBattleEffect::RESURRECT, AudioPath::builtin("RESURECT"), attackedInfo.defender->getPosition());
 				addNewAnim(new ResurrectionAnimation(owner, attackedInfo.defender));
 			});
 		}
@@ -592,7 +593,7 @@ void BattleStacksController::stackAttacking( const StackAttackInfo & info )
 	{
 		owner.addToAnimationStage(EAnimationEvents::BEFORE_HIT, [=]() {
 			owner.appendBattleLog(info.attacker->formatGeneralMessage(-45));
-			owner.effectsController->displayEffect(EBattleEffect::GOOD_LUCK, "GOODLUCK", attacker->getPosition());
+			owner.effectsController->displayEffect(EBattleEffect::GOOD_LUCK, AudioPath::builtin("GOODLUCK"), attacker->getPosition());
 		});
 	}
 
@@ -600,7 +601,7 @@ void BattleStacksController::stackAttacking( const StackAttackInfo & info )
 	{
 		owner.addToAnimationStage(EAnimationEvents::BEFORE_HIT, [=]() {
 			owner.appendBattleLog(info.attacker->formatGeneralMessage(-44));
-			owner.effectsController->displayEffect(EBattleEffect::BAD_LUCK, "BADLUCK", attacker->getPosition());
+			owner.effectsController->displayEffect(EBattleEffect::BAD_LUCK, AudioPath::builtin("BADLUCK"), attacker->getPosition());
 		});
 	}
 
@@ -608,7 +609,7 @@ void BattleStacksController::stackAttacking( const StackAttackInfo & info )
 	{
 		owner.addToAnimationStage(EAnimationEvents::BEFORE_HIT, [=]() {
 			owner.appendBattleLog(info.attacker->formatGeneralMessage(365));
-			owner.effectsController->displayEffect(EBattleEffect::DEATH_BLOW, "DEATHBLO", defender->getPosition());
+			owner.effectsController->displayEffect(EBattleEffect::DEATH_BLOW, AudioPath::builtin("DEATHBLO"), defender->getPosition());
 		});
 
 		for(auto elem : info.secondaryDefender)
@@ -643,7 +644,7 @@ void BattleStacksController::stackAttacking( const StackAttackInfo & info )
 	{
 		owner.addToAnimationStage(EAnimationEvents::AFTER_HIT, [=]()
 		{
-			owner.effectsController->displayEffect(EBattleEffect::DRAIN_LIFE, "DRAINLIF", attacker->getPosition());
+			owner.effectsController->displayEffect(EBattleEffect::DRAIN_LIFE, AudioPath::builtin("DRAINLIF"), attacker->getPosition());
 		});
 	}
 

+ 11 - 10
client/battle/BattleWindow.cpp

@@ -29,6 +29,7 @@
 #include "../windows/CMessage.h"
 #include "../render/CAnimation.h"
 #include "../render/Canvas.h"
+#include "../render/IRenderHandler.h"
 #include "../adventureMap/CInGameConsole.h"
 
 #include "../../CCallback.h"
@@ -37,7 +38,7 @@
 #include "../../lib/mapObjects/CGHeroInstance.h"
 #include "../../lib/CStack.h"
 #include "../../lib/CConfigHandler.h"
-#include "../../lib/filesystem/ResourceID.h"
+#include "../../lib/filesystem/ResourcePath.h"
 #include "../windows/settings/SettingsMainWindow.h"
 
 BattleWindow::BattleWindow(BattleInterface & owner):
@@ -51,7 +52,7 @@ BattleWindow::BattleWindow(BattleInterface & owner):
 
 	REGISTER_BUILDER("battleConsole", &BattleWindow::buildBattleConsole);
 	
-	const JsonNode config(ResourceID("config/widgets/BattleWindow2.json"));
+	const JsonNode config(JsonPath::builtin("config/widgets/BattleWindow2.json"));
 	
 	addShortcut(EShortcut::GLOBAL_OPTIONS, std::bind(&BattleWindow::bOptionsf, this));
 	addShortcut(EShortcut::BATTLE_SURRENDER, std::bind(&BattleWindow::bSurrenderf, this));
@@ -436,23 +437,23 @@ void BattleWindow::showAlternativeActionIcon(PossiblePlayerBattleAction action)
 	if(!w)
 		return;
 	
-	std::string iconName = variables["actionIconDefault"].String();
+	AnimationPath iconName = AnimationPath::fromJson(variables["actionIconDefault"]);
 	switch(action.get())
 	{
 		case PossiblePlayerBattleAction::ATTACK:
-			iconName = variables["actionIconAttack"].String();
+			iconName = AnimationPath::fromJson(variables["actionIconAttack"]);
 			break;
 			
 		case PossiblePlayerBattleAction::SHOOT:
-			iconName = variables["actionIconShoot"].String();
+			iconName = AnimationPath::fromJson(variables["actionIconShoot"]);
 			break;
 			
 		case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
-			iconName = variables["actionIconSpell"].String();
+			iconName = AnimationPath::fromJson(variables["actionIconSpell"]);
 			break;
 
 		case PossiblePlayerBattleAction::ANY_LOCATION:
-			iconName = variables["actionIconSpell"].String();
+			iconName = AnimationPath::fromJson(variables["actionIconSpell"]);
 			break;
 			
 		//TODO: figure out purpose of this icon
@@ -461,15 +462,15 @@ void BattleWindow::showAlternativeActionIcon(PossiblePlayerBattleAction action)
 			//break;
 			
 		case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
-			iconName = variables["actionIconReturn"].String();
+			iconName = AnimationPath::fromJson(variables["actionIconReturn"]);
 			break;
 			
 		case PossiblePlayerBattleAction::WALK_AND_ATTACK:
-			iconName = variables["actionIconNoReturn"].String();
+			iconName = AnimationPath::fromJson(variables["actionIconNoReturn"]);
 			break;
 	}
 		
-	auto anim = std::make_shared<CAnimation>(iconName);
+	auto anim = GH.renderHandler().loadAnimation(iconName);
 	w->setImage(anim);
 	w->redraw();
 }

+ 5 - 3
client/battle/CreatureAnimation.cpp

@@ -13,8 +13,10 @@
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CCreatureHandler.h"
 
+#include "../gui/CGuiHandler.h"
 #include "../render/Canvas.h"
 #include "../render/ColorFilter.h"
+#include "../render/IRenderHandler.h"
 
 static const ColorRGBA creatureBlueBorder = { 0, 255, 255, 255 };
 static const ColorRGBA creatureGoldBorder = { 255, 255, 0, 255 };
@@ -185,7 +187,7 @@ void CreatureAnimation::setType(ECreatureAnimType type)
 	speed = speedController(this, type);
 }
 
-CreatureAnimation::CreatureAnimation(const std::string & name_, TSpeedController controller)
+CreatureAnimation::CreatureAnimation(const AnimationPath & name_, TSpeedController controller)
 	: name(name_),
 	  speed(0.1f),
 	  shadowAlpha(128),
@@ -196,8 +198,8 @@ CreatureAnimation::CreatureAnimation(const std::string & name_, TSpeedController
 	  speedController(controller),
 	  once(false)
 {
-	forward = std::make_shared<CAnimation>(name_);
-	reverse = std::make_shared<CAnimation>(name_);
+	forward = GH.renderHandler().loadAnimation(name_);
+	reverse = GH.renderHandler().loadAnimation(name_);
 
 	//todo: optimize
 	forward->preload();

+ 2 - 2
client/battle/CreatureAnimation.h

@@ -70,7 +70,7 @@ public:
 	using TSpeedController = std::function<float(CreatureAnimation *, ECreatureAnimType)>;
 
 private:
-	std::string name;
+	AnimationPath name;
 
 	/// animation for rendering stack in default orientation - facing right
 	std::shared_ptr<CAnimation> forward;
@@ -122,7 +122,7 @@ public:
 	/// name - path to .def file, relative to SPRITES/ directory
 	/// controller - function that will return for how long *each* frame
 	/// in specified group of animation should be played, measured in seconds
-	CreatureAnimation(const std::string & name_, TSpeedController speedController);
+	CreatureAnimation(const AnimationPath & name_, TSpeedController speedController);
 
 	/// sets type of animation and resets framecount
 	void setType(ECreatureAnimType type);

+ 7 - 0
client/gui/CGuiHandler.cpp

@@ -25,6 +25,7 @@
 #include "../render/IFont.h"
 #include "../render/EFont.h"
 #include "../renderSDL/ScreenHandler.h"
+#include "../renderSDL/RenderHandler.h"
 #include "../CMT.h"
 #include "../CPlayerInterface.h"
 #include "../battle/BattleInterface.h"
@@ -75,6 +76,7 @@ void CGuiHandler::init()
 	eventDispatcherInstance = std::make_unique<EventDispatcher>();
 	windowHandlerInstance = std::make_unique<WindowHandler>();
 	screenHandlerInstance = std::make_unique<ScreenHandler>();
+	renderHandlerInstance = std::make_unique<RenderHandler>();
 	shortcutsHandlerInstance = std::make_unique<ShortcutHandler>();
 	framerateManagerInstance = std::make_unique<FramerateManager>(settings["video"]["targetfps"].Integer());
 }
@@ -206,6 +208,11 @@ IScreenHandler & CGuiHandler::screenHandler()
 	return *screenHandlerInstance;
 }
 
+IRenderHandler & CGuiHandler::renderHandler()
+{
+	return *renderHandlerInstance;
+}
+
 EventDispatcher & CGuiHandler::events()
 {
 	return *eventDispatcherInstance;

+ 3 - 1
client/gui/CGuiHandler.h

@@ -22,6 +22,7 @@ class IStatusBar;
 class CIntObject;
 class IUpdateable;
 class IShowActivatable;
+class IRenderHandler;
 class IScreenHandler;
 class WindowHandler;
 class EventDispatcher;
@@ -41,6 +42,7 @@ private:
 	std::unique_ptr<WindowHandler> windowHandlerInstance;
 
 	std::unique_ptr<IScreenHandler> screenHandlerInstance;
+	std::unique_ptr<IRenderHandler> renderHandlerInstance;
 	std::unique_ptr<FramerateManager> framerateManagerInstance;
 	std::unique_ptr<EventDispatcher> eventDispatcherInstance;
 	std::unique_ptr<InputHandler> inputHandlerInstance;
@@ -67,7 +69,7 @@ public:
 	void stopTextInput();
 
 	IScreenHandler & screenHandler();
-
+	IRenderHandler & renderHandler();
 	WindowHandler & windows();
 
 	/// Returns currently active status bar. Guaranteed to be non-null

+ 9 - 8
client/gui/CursorHandler.cpp

@@ -17,6 +17,7 @@
 #include "../renderSDL/CursorHardware.h"
 #include "../render/CAnimation.h"
 #include "../render/IImage.h"
+#include "../render/IRenderHandler.h"
 
 #include "../../lib/CConfigHandler.h"
 
@@ -46,10 +47,10 @@ CursorHandler::CursorHandler()
 
 	cursors =
 	{
-		std::make_unique<CAnimation>("CRADVNTR"),
-		std::make_unique<CAnimation>("CRCOMBAT"),
-		std::make_unique<CAnimation>("CRDEFLT"),
-		std::make_unique<CAnimation>("CRSPELL")
+		GH.renderHandler().loadAnimation(AnimationPath::builtin("CRADVNTR")),
+		GH.renderHandler().loadAnimation(AnimationPath::builtin("CRCOMBAT")),
+		GH.renderHandler().loadAnimation(AnimationPath::builtin("CRDEFLT")),
+		GH.renderHandler().loadAnimation(AnimationPath::builtin("CRSPELL"))
 	};
 
 	for (auto & cursor : cursors)
@@ -100,11 +101,11 @@ void CursorHandler::dragAndDropCursor(std::shared_ptr<IImage> image)
 	cursor->setImage(getCurrentImage(), getPivotOffset());
 }
 
-void CursorHandler::dragAndDropCursor (std::string path, size_t index)
+void CursorHandler::dragAndDropCursor (const AnimationPath & path, size_t index)
 {
-	CAnimation anim(path);
-	anim.load(index);
-	dragAndDropCursor(anim.getImage(index));
+	auto anim = GH.renderHandler().loadAnimation(path);
+	anim->load(index);
+	dragAndDropCursor(anim->getImage(index));
 }
 
 void CursorHandler::cursorMove(const int & x, const int & y)

+ 3 - 2
client/gui/CursorHandler.h

@@ -10,6 +10,7 @@
 #pragma once
 
 #include "../../lib/Point.h"
+#include "../../lib/filesystem/ResourcePath.h"
 
 class ICursor;
 class IImage;
@@ -113,7 +114,7 @@ class CursorHandler final
 {
 	std::shared_ptr<IImage> dndObject; //if set, overrides currentCursor
 
-	std::array<std::unique_ptr<CAnimation>, 4> cursors;
+	std::array<std::shared_ptr<CAnimation>, 4> cursors;
 
 	bool showing;
 
@@ -143,7 +144,7 @@ public:
 	/// @param image Image to replace cursor with or nullptr to use the normal cursor.
 	void dragAndDropCursor(std::shared_ptr<IImage> image);
 
-	void dragAndDropCursor(std::string path, size_t index);
+	void dragAndDropCursor(const AnimationPath & path, size_t index);
 
 	/// Changes cursor to specified index
 	void set(Cursor::Default index);

+ 10 - 10
client/gui/InterfaceObjectConfigurable.cpp

@@ -31,7 +31,7 @@
 
 #include "../../lib//constants/StringConstants.h"
 #include "../../lib/CGeneralTextHandler.h"
-#include "../../lib/filesystem/ResourceID.h"
+#include "../../lib/filesystem/ResourcePath.h"
 
 InterfaceObjectConfigurable::InterfaceObjectConfigurable(const JsonNode & config, int used, Point offset):
 	InterfaceObjectConfigurable(used, offset)
@@ -110,7 +110,7 @@ void InterfaceObjectConfigurable::build(const JsonNode &config)
 	{
 		if (!config["library"].isNull())
 		{
-			const JsonNode library(ResourceID(config["library"].String()));
+			const JsonNode library(JsonPath::fromJson(config["library"]));
 			loadCustomBuilders(library);
 		}
 
@@ -305,7 +305,7 @@ EShortcut InterfaceObjectConfigurable::readHotkey(const JsonNode & config) const
 std::shared_ptr<CPicture> InterfaceObjectConfigurable::buildPicture(const JsonNode & config) const
 {
 	logGlobal->debug("Building widget CPicture");
-	auto image = config["image"].String();
+	auto image = ImagePath::fromJson(config["image"]);
 	auto position = readPosition(config["position"]);
 	auto pic = std::make_shared<CPicture>(image, position.x, position.y);
 	if(!config["visible"].isNull())
@@ -369,7 +369,7 @@ std::shared_ptr<CToggleButton> InterfaceObjectConfigurable::buildToggleButton(co
 {
 	logGlobal->debug("Building widget CToggleButton");
 	auto position = readPosition(config["position"]);
-	auto image = config["image"].String();
+	auto image = AnimationPath::fromJson(config["image"]);
 	auto help = readHintText(config["help"]);
 	auto button = std::make_shared<CToggleButton>(position, image, help);
 	if(!config["items"].isNull())
@@ -395,7 +395,7 @@ std::shared_ptr<CButton> InterfaceObjectConfigurable::buildButton(const JsonNode
 {
 	logGlobal->debug("Building widget CButton");
 	auto position = readPosition(config["position"]);
-	auto image = config["image"].String();
+	auto image = AnimationPath::fromJson(config["image"]);
 	auto help = readHintText(config["help"]);
 	auto button = std::make_shared<CButton>(position, image, help);
 	if(!config["items"].isNull())
@@ -522,7 +522,7 @@ std::shared_ptr<CAnimImage> InterfaceObjectConfigurable::buildImage(const JsonNo
 {
 	logGlobal->debug("Building widget CAnimImage");
 	auto position = readPosition(config["position"]);
-	auto image = config["image"].String();
+	auto image = AnimationPath::fromJson(config["image"]);
 	int group = config["group"].isNull() ? 0 : config["group"].Integer();
 	int frame = config["frame"].isNull() ? 0 : config["frame"].Integer();
 	return std::make_shared<CAnimImage>(image, frame, group, position.x, position.y);
@@ -531,7 +531,7 @@ std::shared_ptr<CAnimImage> InterfaceObjectConfigurable::buildImage(const JsonNo
 std::shared_ptr<CFilledTexture> InterfaceObjectConfigurable::buildTexture(const JsonNode & config) const
 {
 	logGlobal->debug("Building widget CFilledTexture");
-	auto image = config["image"].String();
+	auto image = ImagePath::fromJson(config["image"]);
 	auto rect = readRect(config["rect"]);
 	auto playerColor = readPlayerColor(config["color"]);
 	if(playerColor.isValidPlayer())
@@ -546,7 +546,7 @@ std::shared_ptr<ComboBox> InterfaceObjectConfigurable::buildComboBox(const JsonN
 {
 	logGlobal->debug("Building widget ComboBox");
 	auto position = readPosition(config["position"]);
-	auto image = config["image"].String();
+	auto image = AnimationPath::fromJson(config["image"]);
 	auto help = readHintText(config["help"]);
 	auto result = std::make_shared<ComboBox>(position, image, help, config["dropDown"]);
 	if(!config["items"].isNull())
@@ -573,7 +573,7 @@ std::shared_ptr<CTextInput> InterfaceObjectConfigurable::buildTextInput(const Js
 	logGlobal->debug("Building widget CTextInput");
 	auto rect = readRect(config["rect"]);
 	auto offset = readPosition(config["backgroundOffset"]);
-	auto bgName = config["background"].String();
+	auto bgName = ImagePath::fromJson(config["background"]);
 	auto result = std::make_shared<CTextInput>(rect, offset, bgName, 0);
 	if(!config["alignment"].isNull())
 		result->alignment = readTextAlignment(config["alignment"]);
@@ -664,7 +664,7 @@ std::shared_ptr<CShowableAnim> InterfaceObjectConfigurable::buildAnimation(const
 {
 	logGlobal->debug("Building widget CShowableAnim");
 	auto position = readPosition(config["position"]);
-	auto image = config["image"].String();
+	auto image = AnimationPath::fromJson(config["image"]);
 	ui8 flags = 0;
 	if(!config["repeat"].Bool())
 		flags |= CShowableAnim::EFlags::PLAY_ONCE;

+ 13 - 13
client/lobby/CBonusSelection.cpp

@@ -31,6 +31,7 @@
 #include "../windows/GUIClasses.h"
 #include "../windows/InfoWindows.h"
 #include "../render/IImage.h"
+#include "../render/IRenderHandler.h"
 #include "../render/CAnimation.h"
 #include "../render/Graphics.h"
 #include "../gui/CGuiHandler.h"
@@ -65,18 +66,17 @@ CBonusSelection::CBonusSelection()
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
 
-	std::string bgName = getCampaign()->getRegions().getBackgroundName();
-	setBackground(bgName);
+	setBackground(getCampaign()->getRegions().getBackgroundName());
 
-	panelBackground = std::make_shared<CPicture>("CAMPBRF.BMP", 456, 6);
+	panelBackground = std::make_shared<CPicture>(ImagePath::builtin("CAMPBRF.BMP"), 456, 6);
 
-	buttonStart = std::make_shared<CButton>(Point(475, 536), "CBBEGIB.DEF", CButton::tooltip(), std::bind(&CBonusSelection::startMap, this), EShortcut::GLOBAL_ACCEPT);
-	buttonRestart = std::make_shared<CButton>(Point(475, 536), "CBRESTB.DEF", CButton::tooltip(), std::bind(&CBonusSelection::restartMap, this), EShortcut::GLOBAL_ACCEPT);
-	buttonBack = std::make_shared<CButton>(Point(624, 536), "CBCANCB.DEF", CButton::tooltip(), std::bind(&CBonusSelection::goBack, this), EShortcut::GLOBAL_CANCEL);
+	buttonStart = std::make_shared<CButton>(Point(475, 536), AnimationPath::builtin("CBBEGIB.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::startMap, this), EShortcut::GLOBAL_ACCEPT);
+	buttonRestart = std::make_shared<CButton>(Point(475, 536), AnimationPath::builtin("CBRESTB.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::restartMap, this), EShortcut::GLOBAL_ACCEPT);
+	buttonBack = std::make_shared<CButton>(Point(624, 536), AnimationPath::builtin("CBCANCB.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::goBack, this), EShortcut::GLOBAL_CANCEL);
 
 	campaignName = std::make_shared<CLabel>(481, 28, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW, CSH->si->getCampaignName());
 
-	iconsMapSizes = std::make_shared<CAnimImage>("SCNRMPSZ", 4, 0, 735, 26);
+	iconsMapSizes = std::make_shared<CAnimImage>(AnimationPath::builtin("SCNRMPSZ"), 4, 0, 735, 26);
 
 	labelCampaignDescription = std::make_shared<CLabel>(481, 63, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[38]);
 	campaignDescription = std::make_shared<CTextBox>(getCampaign()->getDescription(), Rect(480, 86, 286, 117), 1);
@@ -97,13 +97,13 @@ CBonusSelection::CBonusSelection()
 
 	for(size_t b = 0; b < difficultyIcons.size(); ++b)
 	{
-		difficultyIcons[b] = std::make_shared<CAnimImage>("GSPBUT" + std::to_string(b + 3) + ".DEF", 0, 0, 709, 455);
+		difficultyIcons[b] = std::make_shared<CAnimImage>(AnimationPath::builtinTODO("GSPBUT" + std::to_string(b + 3) + ".DEF"), 0, 0, 709, 455);
 	}
 
 	if(getCampaign()->playerSelectedDifficulty())
 	{
-		buttonDifficultyLeft = std::make_shared<CButton>(Point(694, 508), "SCNRBLF.DEF", CButton::tooltip(), std::bind(&CBonusSelection::decreaseDifficulty, this));
-		buttonDifficultyRight = std::make_shared<CButton>(Point(738, 508), "SCNRBRT.DEF", CButton::tooltip(), std::bind(&CBonusSelection::increaseDifficulty, this));
+		buttonDifficultyLeft = std::make_shared<CButton>(Point(694, 508), AnimationPath::builtin("SCNRBLF.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::decreaseDifficulty, this));
+		buttonDifficultyRight = std::make_shared<CButton>(Point(738, 508), AnimationPath::builtin("SCNRBRT.DEF"), CButton::tooltip(), std::bind(&CBonusSelection::increaseDifficulty, this));
 	}
 
 	for(auto scenarioID : getCampaign()->allScenarios())
@@ -115,7 +115,7 @@ CBonusSelection::CBonusSelection()
 	}
 
 	if (!getCampaign()->getMusic().empty())
-		CCS->musich->playMusic( "Music/" + getCampaign()->getMusic(), true, false);
+		CCS->musich->playMusic( getCampaign()->getMusic(), true, false);
 }
 
 void CBonusSelection::createBonusesIcons()
@@ -302,12 +302,12 @@ void CBonusSelection::createBonusesIcons()
 			break;
 		}
 
-		std::shared_ptr<CToggleButton> bonusButton = std::make_shared<CToggleButton>(Point(475 + i * 68, 455), "", CButton::tooltip(desc, desc));
+		std::shared_ptr<CToggleButton> bonusButton = std::make_shared<CToggleButton>(Point(475 + i * 68, 455), AnimationPath(), CButton::tooltip(desc, desc));
 
 		if(picNumber != -1)
 			picName += ":" + std::to_string(picNumber);
 
-		auto anim = std::make_shared<CAnimation>();
+		auto anim = GH.renderHandler().createAnimation();
 		anim->setCustom(picName, 0);
 		bonusButton->setImage(anim);
 		if(CSH->campaignBonus == i)

+ 8 - 8
client/lobby/CLobbyScreen.cpp

@@ -43,17 +43,17 @@ CLobbyScreen::CLobbyScreen(ESelectionScreen screenType)
 	{
 		tabSel->callOnSelect = std::bind(&IServerAPI::setMapInfo, CSH, _1, nullptr);
 
-		buttonSelect = std::make_shared<CButton>(Point(411, 80), "GSPBUTT.DEF", CGI->generaltexth->zelp[45], 0, EShortcut::LOBBY_SELECT_SCENARIO);
+		buttonSelect = std::make_shared<CButton>(Point(411, 80), AnimationPath::builtin("GSPBUTT.DEF"), CGI->generaltexth->zelp[45], 0, EShortcut::LOBBY_SELECT_SCENARIO);
 		buttonSelect->addCallback([&]()
 		{
 			toggleTab(tabSel);
 			CSH->setMapInfo(tabSel->getSelectedMapInfo());
 		});
 
-		buttonOptions = std::make_shared<CButton>(Point(411, 510), "GSPBUTT.DEF", CGI->generaltexth->zelp[46], std::bind(&CLobbyScreen::toggleTab, this, tabOpt), EShortcut::LOBBY_ADDITIONAL_OPTIONS);
+		buttonOptions = std::make_shared<CButton>(Point(411, 510), AnimationPath::builtin("GSPBUTT.DEF"), CGI->generaltexth->zelp[46], std::bind(&CLobbyScreen::toggleTab, this, tabOpt), EShortcut::LOBBY_ADDITIONAL_OPTIONS);
 	};
 
-	buttonChat = std::make_shared<CButton>(Point(619, 80), "GSPBUT2.DEF", CGI->generaltexth->zelp[48], std::bind(&CLobbyScreen::toggleChat, this), EShortcut::LOBBY_HIDE_CHAT);
+	buttonChat = std::make_shared<CButton>(Point(619, 80), AnimationPath::builtin("GSPBUT2.DEF"), CGI->generaltexth->zelp[48], std::bind(&CLobbyScreen::toggleChat, this), EShortcut::LOBBY_HIDE_CHAT);
 	buttonChat->addTextOverlay(CGI->generaltexth->allTexts[532], FONT_SMALL, Colors::WHITE);
 
 	switch(screenType)
@@ -63,7 +63,7 @@ CLobbyScreen::CLobbyScreen(ESelectionScreen screenType)
 		tabOpt = std::make_shared<OptionsTab>();
 		tabRand = std::make_shared<RandomMapTab>();
 		tabRand->mapInfoChanged += std::bind(&IServerAPI::setMapInfo, CSH, _1, _2);
-		buttonRMG = std::make_shared<CButton>(Point(411, 105), "GSPBUTT.DEF", CGI->generaltexth->zelp[47], 0, EShortcut::LOBBY_RANDOM_MAP);
+		buttonRMG = std::make_shared<CButton>(Point(411, 105), AnimationPath::builtin("GSPBUTT.DEF"), CGI->generaltexth->zelp[47], 0, EShortcut::LOBBY_RANDOM_MAP);
 		buttonRMG->addCallback([&]()
 		{
 			toggleTab(tabRand);
@@ -72,24 +72,24 @@ CLobbyScreen::CLobbyScreen(ESelectionScreen screenType)
 
 		card->iconDifficulty->addCallback(std::bind(&IServerAPI::setDifficulty, CSH, _1));
 
-		buttonStart = std::make_shared<CButton>(Point(411, 535), "SCNRBEG.DEF", CGI->generaltexth->zelp[103], std::bind(&CLobbyScreen::startScenario, this, true), EShortcut::LOBBY_BEGIN_GAME);
+		buttonStart = std::make_shared<CButton>(Point(411, 535), AnimationPath::builtin("SCNRBEG.DEF"), CGI->generaltexth->zelp[103], std::bind(&CLobbyScreen::startScenario, this, true), EShortcut::LOBBY_BEGIN_GAME);
 		initLobby();
 		break;
 	}
 	case ESelectionScreen::loadGame:
 	{
 		tabOpt = std::make_shared<OptionsTab>();
-		buttonStart = std::make_shared<CButton>(Point(411, 535), "SCNRLOD.DEF", CGI->generaltexth->zelp[103], std::bind(&CLobbyScreen::startScenario, this, true), EShortcut::LOBBY_LOAD_GAME);
+		buttonStart = std::make_shared<CButton>(Point(411, 535), AnimationPath::builtin("SCNRLOD.DEF"), CGI->generaltexth->zelp[103], std::bind(&CLobbyScreen::startScenario, this, true), EShortcut::LOBBY_LOAD_GAME);
 		initLobby();
 		break;
 	}
 	case ESelectionScreen::campaignList:
 		tabSel->callOnSelect = std::bind(&IServerAPI::setMapInfo, CSH, _1, nullptr);
-		buttonStart = std::make_shared<CButton>(Point(411, 535), "SCNRLOD.DEF", CButton::tooltip(), std::bind(&CLobbyScreen::startCampaign, this), EShortcut::LOBBY_BEGIN_GAME);
+		buttonStart = std::make_shared<CButton>(Point(411, 535), AnimationPath::builtin("SCNRLOD.DEF"), CButton::tooltip(), std::bind(&CLobbyScreen::startCampaign, this), EShortcut::LOBBY_BEGIN_GAME);
 		break;
 	}
 
-	buttonBack = std::make_shared<CButton>(Point(581, 535), "SCNRBACK.DEF", CGI->generaltexth->zelp[105], [&]()
+	buttonBack = std::make_shared<CButton>(Point(581, 535), AnimationPath::builtin("SCNRBACK.DEF"), CGI->generaltexth->zelp[105], [&]()
 	{
 		CSH->sendClientDisconnecting();
 		close();

+ 2 - 2
client/lobby/CSavingScreen.cpp

@@ -40,7 +40,7 @@ CSavingScreen::CSavingScreen()
 	tabSel->toggleMode();
 	curTab = tabSel;
 		
-	buttonStart = std::make_shared<CButton>(Point(411, 535), "SCNRSAV.DEF", CGI->generaltexth->zelp[103], std::bind(&CSavingScreen::saveGame, this), EShortcut::LOBBY_SAVE_GAME);
+	buttonStart = std::make_shared<CButton>(Point(411, 535), AnimationPath::builtin("SCNRSAV.DEF"), CGI->generaltexth->zelp[103], std::bind(&CSavingScreen::saveGame, this), EShortcut::LOBBY_SAVE_GAME);
 }
 
 const CMapInfo * CSavingScreen::getMapInfo()
@@ -80,7 +80,7 @@ void CSavingScreen::saveGame()
 		close();
 	};
 
-	if(CResourceHandler::get("local")->existsResource(ResourceID(path, EResType::SAVEGAME)))
+	if(CResourceHandler::get("local")->existsResource(ResourcePath(path, EResType::SAVEGAME)))
 	{
 		std::string hlp = CGI->generaltexth->allTexts[493]; //%s exists. Overwrite?
 		boost::algorithm::replace_first(hlp, "%s", tabSel->inputName->getText());

+ 1 - 1
client/lobby/CScenarioInfoScreen.cpp

@@ -45,7 +45,7 @@ CScenarioInfoScreen::CScenarioInfoScreen()
 	card->changeSelection();
 
 	card->iconDifficulty->setSelected(getCurrentDifficulty());
-	buttonBack = std::make_shared<CButton>(Point(584, 535), "SCNRBACK.DEF", CGI->generaltexth->zelp[105], [=](){ close();}, EShortcut::GLOBAL_CANCEL);
+	buttonBack = std::make_shared<CButton>(Point(584, 535), AnimationPath::builtin("SCNRBACK.DEF"), CGI->generaltexth->zelp[105], [=](){ close();}, EShortcut::GLOBAL_CANCEL);
 }
 
 CScenarioInfoScreen::~CScenarioInfoScreen()

+ 13 - 12
client/lobby/CSelectionBase.cpp

@@ -39,6 +39,7 @@
 #include "../render/CAnimation.h"
 #include "../render/Graphics.h"
 #include "../render/IFont.h"
+#include "../render/IRenderHandler.h"
 
 #include "../../lib/NetPacksLobby.h"
 #include "../../lib/CGeneralTextHandler.h"
@@ -80,16 +81,16 @@ CSelectionBase::CSelectionBase(ESelectionScreen type)
 	pos.h = 584;
 	if(screenType == ESelectionScreen::campaignList)
 	{
-		setBackground("CamCust.bmp");
+		setBackground(ImagePath::builtin("CamCust.bmp"));
 	}
 	else
 	{
 		const JsonVector & bgNames = CMainMenuConfig::get().getConfig()["game-select"].Vector();
-		setBackground(RandomGeneratorUtil::nextItem(bgNames, CRandomGenerator::getDefault())->String());
+		setBackground(ImagePath::fromJson(*RandomGeneratorUtil::nextItem(bgNames, CRandomGenerator::getDefault())));
 	}
 	pos = background->center();
 	card = std::make_shared<InfoCard>();
-	buttonBack = std::make_shared<CButton>(Point(581, 535), "SCNRBACK.DEF", CGI->generaltexth->zelp[105], [=](){ close();}, EShortcut::GLOBAL_CANCEL);
+	buttonBack = std::make_shared<CButton>(Point(581, 535), AnimationPath::builtin("SCNRBACK.DEF"), CGI->generaltexth->zelp[105], [=](){ close();}, EShortcut::GLOBAL_CANCEL);
 }
 
 void CSelectionBase::toggleTab(std::shared_ptr<CIntObject> tab)
@@ -125,7 +126,7 @@ InfoCard::InfoCard()
 	mapName = std::make_shared<CLabel>(26, 39, FONT_BIG, ETextAlignment::TOPLEFT, Colors::YELLOW);
 	Rect descriptionRect(26, 149, 320, 115);
 	mapDescription = std::make_shared<CTextBox>("", descriptionRect, 1);
-	playerListBg = std::make_shared<CPicture>("CHATPLUG.bmp", 16, 276);
+	playerListBg = std::make_shared<CPicture>(ImagePath::builtin("CHATPLUG.bmp"), 16, 276);
 	chat = std::make_shared<CChatBox>(Rect(26, 132, 340, 132));
 
 	if(SEL->screenType == ESelectionScreen::campaignList)
@@ -134,14 +135,14 @@ InfoCard::InfoCard()
 	}
 	else
 	{
-		background = std::make_shared<CPicture>("GSELPOP1.bmp", 0, 0);
+		background = std::make_shared<CPicture>(ImagePath::builtin("GSELPOP1.bmp"), 0, 0);
 		parent->addChild(background.get());
 		auto it = vstd::find(parent->children, this); //our position among parent children
 		parent->children.insert(it, background.get()); //put BG before us
 		parent->children.pop_back();
 		pos.w = background->pos.w;
 		pos.h = background->pos.h;
-		iconsMapSizes = std::make_shared<CAnimImage>("SCNRMPSZ", 4, 0, 318, 22); //let it be custom size (frame 4) by default
+		iconsMapSizes = std::make_shared<CAnimImage>(AnimationPath::builtin("SCNRMPSZ"), 4, 0, 318, 22); //let it be custom size (frame 4) by default
 
 		iconDifficulty = std::make_shared<CToggleGroup>(0);
 		{
@@ -149,7 +150,7 @@ InfoCard::InfoCard()
 
 			for(int i = 0; i < 5; i++)
 			{
-				auto button = std::make_shared<CToggleButton>(Point(110 + i * 32, 450), difButns[i], CGI->generaltexth->zelp[24 + i]);
+				auto button = std::make_shared<CToggleButton>(Point(110 + i * 32, 450), AnimationPath::builtin(difButns[i]), CGI->generaltexth->zelp[24 + i]);
 
 				iconDifficulty->addToggle(i, button);
 				if(SEL->screenType != ESelectionScreen::newGame)
@@ -165,8 +166,8 @@ InfoCard::InfoCard()
 		labelScenarioDescription = std::make_shared<CLabel>(26, 132, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[496]);
 		labelVictoryCondition = std::make_shared<CLabel>(26, 283, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[497]);
 		labelLossCondition = std::make_shared<CLabel>(26, 339, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[498]);
-		iconsVictoryCondition = std::make_shared<CAnimImage>("SCNRVICT", 0, 0, 24, 302);
-		iconsLossCondition = std::make_shared<CAnimImage>("SCNRLOSS", 0, 0, 24, 359);
+		iconsVictoryCondition = std::make_shared<CAnimImage>(AnimationPath::builtin("SCNRVICT"), 0, 0, 24, 302);
+		iconsLossCondition = std::make_shared<CAnimImage>(AnimationPath::builtin("SCNRLOSS"), 0, 0, 24, 359);
 
 		labelVictoryConditionText = std::make_shared<CLabel>(60, 307, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE);
 		labelLossConditionText = std::make_shared<CLabel>(60, 366, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE);
@@ -339,7 +340,7 @@ void CChatBox::keyPressed(EShortcut key)
 
 void CChatBox::addNewMessage(const std::string & text)
 {
-	CCS->soundh->playSound("CHAT");
+	CCS->soundh->playSound(AudioPath::builtin("CHAT"));
 	chatHistory->setText(chatHistory->label->getText() + text + "\n");
 	if(chatHistory->slider)
 		chatHistory->slider->scrollToMax();
@@ -356,7 +357,7 @@ CFlagBox::CFlagBox(const Rect & rect)
 	labelAllies = std::make_shared<CLabel>(0, 0, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[390] + ":");
 	labelEnemies = std::make_shared<CLabel>(133, 0, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[391] + ":");
 
-	iconsTeamFlags = std::make_shared<CAnimation>("ITGFLAGS.DEF");
+	iconsTeamFlags = GH.renderHandler().loadAnimation(AnimationPath::builtin("ITGFLAGS.DEF"));
 	iconsTeamFlags->preload();
 }
 
@@ -390,7 +391,7 @@ void CFlagBox::showPopupWindow(const Point & cursorPosition)
 }
 
 CFlagBox::CFlagBoxTooltipBox::CFlagBoxTooltipBox(std::shared_ptr<CAnimation> icons)
-	: CWindowObject(BORDERED | RCLICK_POPUP | SHADOW_DISABLED, "DIBOXBCK")
+	: CWindowObject(BORDERED | RCLICK_POPUP | SHADOW_DISABLED, ImagePath::builtin("DIBOXBCK"))
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
 

+ 25 - 25
client/lobby/OptionsTab.cpp

@@ -137,7 +137,7 @@ OptionsTab::OptionsTab() : humanPlayers(0)
 		}
 	});
 	
-	const JsonNode config(ResourceID("config/widgets/optionsTab.json"));
+	const JsonNode config(JsonPath::builtin("config/widgets/optionsTab.json"));
 	build(config);
 	
 	//set timers combo box callbacks
@@ -343,18 +343,18 @@ size_t OptionsTab::CPlayerSettingsHelper::getImageIndex(bool big)
 	return 0;
 }
 
-std::string OptionsTab::CPlayerSettingsHelper::getImageName(bool big)
+AnimationPath OptionsTab::CPlayerSettingsHelper::getImageName(bool big)
 {
 	switch(type)
 	{
 	case OptionsTab::TOWN:
-		return big ? "ITPt": "ITPA";
+		return AnimationPath::builtin(big ? "ITPt": "ITPA");
 	case OptionsTab::HERO:
-		return big ? "PortraitsLarge": "PortraitsSmall";
+		return AnimationPath::builtin(big ? "PortraitsLarge": "PortraitsSmall");
 	case OptionsTab::BONUS:
-		return "SCNRSTAR";
+		return AnimationPath::builtin("SCNRSTAR");
 	}
-	return "";
+	return {};
 }
 
 std::string OptionsTab::CPlayerSettingsHelper::getName()
@@ -553,7 +553,7 @@ OptionsTab::CPlayerOptionTooltipBox::CPlayerOptionTooltipBox(CPlayerSettingsHelp
 
 void OptionsTab::CPlayerOptionTooltipBox::genHeader()
 {
-	backgroundTexture = std::make_shared<CFilledTexture>("DIBOXBCK", pos);
+	backgroundTexture = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), pos);
 	updateShadow();
 
 	labelTitle = std::make_shared<CLabel>(pos.w / 2 + 8, 21, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, getTitle());
@@ -585,7 +585,7 @@ void OptionsTab::CPlayerOptionTooltipBox::genHeroWindow()
 	labelHeroSpeciality = std::make_shared<CLabel>(pos.w / 2 + 4, 117, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[78]);
 	auto heroIndex = settings.hero.getNum() >= CGI->heroh->size() ? 0 : settings.hero.getNum();
 
-	imageSpeciality = std::make_shared<CAnimImage>("UN44", (*CGI->heroh)[heroIndex]->imageIndex, 0, pos.w / 2 - 22, 134);
+	imageSpeciality = std::make_shared<CAnimImage>(AnimationPath::builtin("UN44"), (*CGI->heroh)[heroIndex]->imageIndex, 0, pos.w / 2 - 22, 134);
 	labelSpecialityName = std::make_shared<CLabel>(pos.w / 2, 188, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, (*CGI->heroh)[heroIndex]->getSpecialtyNameTranslated());
 }
 
@@ -717,7 +717,7 @@ void OptionsTab::SelectionWindow::recreate()
 
 	pos = Rect(0, 0, x, y);
 
-	backgroundTexture = std::make_shared<FilledTexturePlayerColored>("DiBoxBck", pos);
+	backgroundTexture = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), pos);
 	backgroundTexture->playerColored(PlayerColor(1));
 	updateShadow();
 
@@ -747,7 +747,7 @@ void OptionsTab::SelectionWindow::genContentGrid(int lines)
 	{
 		for(int x = 0; x < elementsPerLine; x++)
 		{
-			components.push_back(std::make_shared<CPicture>("lobby/townBorderBig", x * (ICON_BIG_WIDTH-1), y * (ICON_BIG_HEIGHT-1)));
+			components.push_back(std::make_shared<CPicture>(ImagePath::builtin("lobby/townBorderBig"), x * (ICON_BIG_WIDTH-1), y * (ICON_BIG_HEIGHT-1)));
 		}
 	}
 }
@@ -763,7 +763,7 @@ void OptionsTab::SelectionWindow::genContentFactions()
 	components.push_back(std::make_shared<CAnimImage>(helper.getImageName(), helper.getImageIndex(), 0, 6, (ICON_SMALL_HEIGHT/2)));
 	drawOutlinedText(TEXT_POS_X, TEXT_POS_Y, (selectedFaction.getNum() == PlayerSettings::RANDOM) ? Colors::YELLOW : Colors::WHITE, helper.getName());
 	if(selectedFaction.getNum() == PlayerSettings::RANDOM)
-		components.push_back(std::make_shared<CPicture>("lobby/townBorderSmallActivated", 6, (ICON_SMALL_HEIGHT/2)));
+		components.push_back(std::make_shared<CPicture>(ImagePath::builtin("lobby/townBorderSmallActivated"), 6, (ICON_SMALL_HEIGHT/2)));
 
 	for(auto & elem : allowedFactions)
 	{
@@ -776,7 +776,7 @@ void OptionsTab::SelectionWindow::genContentFactions()
 		CPlayerSettingsHelper helper = CPlayerSettingsHelper(set, SelType::TOWN);
 
 		components.push_back(std::make_shared<CAnimImage>(helper.getImageName(true), helper.getImageIndex(true), 0, x * (ICON_BIG_WIDTH-1), y * (ICON_BIG_HEIGHT-1)));
-		components.push_back(std::make_shared<CPicture>(selectedFaction == elem ? "lobby/townBorderBigActivated" : "lobby/townBorderBig", x * (ICON_BIG_WIDTH-1), y * (ICON_BIG_HEIGHT-1)));
+		components.push_back(std::make_shared<CPicture>(ImagePath::builtin(selectedFaction == elem ? "lobby/townBorderBigActivated" : "lobby/townBorderBig"), x * (ICON_BIG_WIDTH-1), y * (ICON_BIG_HEIGHT-1)));
 		drawOutlinedText(x * (ICON_BIG_WIDTH-1) + TEXT_POS_X, y * (ICON_BIG_HEIGHT-1) + TEXT_POS_Y, (selectedFaction == elem) ? Colors::YELLOW : Colors::WHITE, helper.getName());
 		factions.push_back(elem);
 
@@ -795,7 +795,7 @@ void OptionsTab::SelectionWindow::genContentHeroes()
 	components.push_back(std::make_shared<CAnimImage>(helper.getImageName(), helper.getImageIndex(), 0, 6, (ICON_SMALL_HEIGHT/2)));
 	drawOutlinedText(TEXT_POS_X, TEXT_POS_Y, (selectedHero.getNum() == PlayerSettings::RANDOM) ? Colors::YELLOW : Colors::WHITE, helper.getName());
 	if(selectedHero.getNum() == PlayerSettings::RANDOM)
-		components.push_back(std::make_shared<CPicture>("lobby/townBorderSmallActivated", 6, (ICON_SMALL_HEIGHT/2)));
+		components.push_back(std::make_shared<CPicture>(ImagePath::builtin("lobby/townBorderSmallActivated"), 6, (ICON_SMALL_HEIGHT/2)));
 
 	for(auto & elem : allowedHeroes)
 	{
@@ -814,11 +814,11 @@ void OptionsTab::SelectionWindow::genContentHeroes()
 
 			components.push_back(std::make_shared<CAnimImage>(helper.getImageName(true), helper.getImageIndex(true), 0, x * (ICON_BIG_WIDTH-1), y * (ICON_BIG_HEIGHT-1)));
 			drawOutlinedText(x * (ICON_BIG_WIDTH-1) + TEXT_POS_X, y * (ICON_BIG_HEIGHT-1) + TEXT_POS_Y, (selectedHero == elem) ? Colors::YELLOW : Colors::WHITE, helper.getName());
-			std::string image = "lobby/townBorderBig";
+			ImagePath image = ImagePath::builtin("lobby/townBorderBig");
 			if(selectedHero == elem)
-				image = "lobby/townBorderBigActivated";
+				image = ImagePath::builtin("lobby/townBorderBigActivated");
 			if(unusableHeroes.count(elem))
-				image = "lobby/townBorderBigGrayedOut";
+				image = ImagePath::builtin("lobby/townBorderBigGrayedOut");
 			components.push_back(std::make_shared<CPicture>(image, x * (ICON_BIG_WIDTH-1), y * (ICON_BIG_HEIGHT-1)));
 			heroes.push_back(elem);
 
@@ -843,7 +843,7 @@ void OptionsTab::SelectionWindow::genContentBonus()
 		drawOutlinedText(x * (ICON_BIG_WIDTH-1) + TEXT_POS_X, y * (ICON_BIG_HEIGHT-1) + TEXT_POS_Y, Colors::WHITE , helper.getName());
 		if(selectedBonus == elem)
 		{
-			components.push_back(std::make_shared<CPicture>("lobby/townBorderSmallActivated", x * (ICON_BIG_WIDTH-1) + 6, y * (ICON_BIG_HEIGHT-1) + (ICON_SMALL_HEIGHT/2)));
+			components.push_back(std::make_shared<CPicture>(ImagePath::builtin("lobby/townBorderSmallActivated"), x * (ICON_BIG_WIDTH-1) + 6, y * (ICON_BIG_HEIGHT-1) + (ICON_SMALL_HEIGHT/2)));
 			drawOutlinedText(x * (ICON_BIG_WIDTH-1) + TEXT_POS_X, y * (ICON_BIG_HEIGHT-1) + TEXT_POS_Y, Colors::YELLOW , helper.getName());
 		}
 
@@ -1075,18 +1075,18 @@ OptionsTab::PlayerOptionsEntry::PlayerOptionsEntry(const PlayerSettings & S, con
 		"ADOPOPNL.bmp", "ADOPPPNL.bmp", "ADOPTPNL.bmp", "ADOPSPNL.bmp"
 	}};
 
-	background = std::make_shared<CPicture>(bgs[s->color.getNum()], 0, 0);
+	background = std::make_shared<CPicture>(ImagePath::builtin(bgs[s->color.getNum()]), 0, 0);
 	labelPlayerName = std::make_shared<CLabel>(55, 10, EFonts::FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, s->name);
 	labelWhoCanPlay = std::make_shared<CMultiLineLabel>(Rect(6, 23, 45, (int)graphics->fonts[EFonts::FONT_TINY]->getLineHeight()*2), EFonts::FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->arraytxt[206 + whoCanPlay]);
 
 	if(SEL->screenType == ESelectionScreen::newGame)
 	{
-		buttonTownLeft = std::make_shared<CButton>(Point(107, 5), "ADOPLFA.DEF", CGI->generaltexth->zelp[132], std::bind(&IServerAPI::setPlayerOption, CSH, LobbyChangePlayerOption::TOWN, -1, s->color));
-		buttonTownRight = std::make_shared<CButton>(Point(168, 5), "ADOPRTA.DEF", CGI->generaltexth->zelp[133], std::bind(&IServerAPI::setPlayerOption, CSH, LobbyChangePlayerOption::TOWN, +1, s->color));
-		buttonHeroLeft = std::make_shared<CButton>(Point(183, 5), "ADOPLFA.DEF", CGI->generaltexth->zelp[148], std::bind(&IServerAPI::setPlayerOption, CSH, LobbyChangePlayerOption::HERO, -1, s->color));
-		buttonHeroRight = std::make_shared<CButton>(Point(244, 5), "ADOPRTA.DEF", CGI->generaltexth->zelp[149], std::bind(&IServerAPI::setPlayerOption, CSH, LobbyChangePlayerOption::HERO, +1, s->color));
-		buttonBonusLeft = std::make_shared<CButton>(Point(259, 5), "ADOPLFA.DEF", CGI->generaltexth->zelp[164], std::bind(&IServerAPI::setPlayerOption, CSH, LobbyChangePlayerOption::BONUS, -1, s->color));
-		buttonBonusRight = std::make_shared<CButton>(Point(320, 5), "ADOPRTA.DEF", CGI->generaltexth->zelp[165], std::bind(&IServerAPI::setPlayerOption, CSH, LobbyChangePlayerOption::BONUS, +1, s->color));
+		buttonTownLeft   = std::make_shared<CButton>(Point(107, 5), AnimationPath::builtin("ADOPLFA.DEF"), CGI->generaltexth->zelp[132], std::bind(&IServerAPI::setPlayerOption, CSH, LobbyChangePlayerOption::TOWN, -1, s->color));
+		buttonTownRight  = std::make_shared<CButton>(Point(168, 5), AnimationPath::builtin("ADOPRTA.DEF"), CGI->generaltexth->zelp[133], std::bind(&IServerAPI::setPlayerOption, CSH, LobbyChangePlayerOption::TOWN, +1, s->color));
+		buttonHeroLeft   = std::make_shared<CButton>(Point(183, 5), AnimationPath::builtin("ADOPLFA.DEF"), CGI->generaltexth->zelp[148], std::bind(&IServerAPI::setPlayerOption, CSH, LobbyChangePlayerOption::HERO, -1, s->color));
+		buttonHeroRight  = std::make_shared<CButton>(Point(244, 5), AnimationPath::builtin("ADOPRTA.DEF"), CGI->generaltexth->zelp[149], std::bind(&IServerAPI::setPlayerOption, CSH, LobbyChangePlayerOption::HERO, +1, s->color));
+		buttonBonusLeft  = std::make_shared<CButton>(Point(259, 5), AnimationPath::builtin("ADOPLFA.DEF"), CGI->generaltexth->zelp[164], std::bind(&IServerAPI::setPlayerOption, CSH, LobbyChangePlayerOption::BONUS, -1, s->color));
+		buttonBonusRight = std::make_shared<CButton>(Point(320, 5), AnimationPath::builtin("ADOPRTA.DEF"), CGI->generaltexth->zelp[165], std::bind(&IServerAPI::setPlayerOption, CSH, LobbyChangePlayerOption::BONUS, +1, s->color));
 	}
 
 	hideUnavailableButtons();
@@ -1095,7 +1095,7 @@ OptionsTab::PlayerOptionsEntry::PlayerOptionsEntry(const PlayerSettings & S, con
 	{
 		flag = std::make_shared<CButton>(
 			Point(-43, 2),
-			flags[s->color.getNum()],
+			AnimationPath::builtin(flags[s->color.getNum()]),
 			CGI->generaltexth->zelp[180],
 			std::bind(&OptionsTab::onSetPlayerClicked, &parentTab, *s)
 		);

+ 1 - 1
client/lobby/OptionsTab.h

@@ -62,7 +62,7 @@ private:
 
 		/// visible image settings
 		size_t getImageIndex(bool big = false);
-		std::string getImageName(bool big = false);
+		AnimationPath getImageName(bool big = false);
 
 		std::string getName(); /// name visible in options dialog
 		std::string getTitle(); /// title in popup box

+ 2 - 2
client/lobby/RandomMapTab.cpp

@@ -118,7 +118,7 @@ RandomMapTab::RandomMapTab():
 		});
 	}
 	
-	const JsonNode config(ResourceID("config/widgets/randomMapTab.json"));
+	const JsonNode config(JsonPath::builtin("config/widgets/randomMapTab.json"));
 	build(config);
 	
 	//set combo box callbacks
@@ -388,7 +388,7 @@ std::vector<int> RandomMapTab::getPossibleMapSizes()
 TeamAlignmentsWidget::TeamAlignmentsWidget(RandomMapTab & randomMapTab):
 	InterfaceObjectConfigurable()
 {
-	const JsonNode config(ResourceID("config/widgets/randomMapTeamsWidget.json"));
+	const JsonNode config(JsonPath::builtin("config/widgets/randomMapTeamsWidget.json"));
 	variables = config["variables"];
 	
 	int humanPlayers = randomMapTab.obtainMapGenOptions().getPlayerCount();

+ 25 - 24
client/lobby/SelectionTab.cpp

@@ -30,6 +30,7 @@
 #include "../render/CAnimation.h"
 #include "../render/Canvas.h"
 #include "../render/IImage.h"
+#include "../render/IRenderHandler.h"
 #include "../render/Graphics.h"
 
 #include "../../CCallback.h"
@@ -158,16 +159,16 @@ SelectionTab::SelectionTab(ESelectionScreen Type)
 	if(tabType != ESelectionScreen::campaignList)
 	{
 		sortingBy = _format;
-		background = std::make_shared<CPicture>("SCSELBCK.bmp", 0, 6);
+		background = std::make_shared<CPicture>(ImagePath::builtin("SCSELBCK.bmp"), 0, 6);
 		pos = background->pos;
-		inputName = std::make_shared<CTextInput>(inputNameRect, Point(-32, -25), "GSSTRIP.bmp", 0);
+		inputName = std::make_shared<CTextInput>(inputNameRect, Point(-32, -25), ImagePath::builtin("GSSTRIP.bmp"), 0);
 		inputName->filters += CTextInput::filenameFilter;
 		labelMapSizes = std::make_shared<CLabel>(87, 62, FONT_SMALL, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[510]);
 
 		int sizes[] = {36, 72, 108, 144, 0};
 		const char * filterIconNmes[] = {"SCSMBUT.DEF", "SCMDBUT.DEF", "SCLGBUT.DEF", "SCXLBUT.DEF", "SCALBUT.DEF"};
 		for(int i = 0; i < 5; i++)
-			buttonsSortBy.push_back(std::make_shared<CButton>(Point(158 + 47 * i, 46), filterIconNmes[i], CGI->generaltexth->zelp[54 + i], std::bind(&SelectionTab::filter, this, sizes[i], true)));
+			buttonsSortBy.push_back(std::make_shared<CButton>(Point(158 + 47 * i, 46), AnimationPath::builtin(filterIconNmes[i]), CGI->generaltexth->zelp[54 + i], std::bind(&SelectionTab::filter, this, sizes[i], true)));
 
 		int xpos[] = {23, 55, 88, 121, 306, 339};
 		const char * sortIconNames[] = {"SCBUTT1.DEF", "SCBUTT2.DEF", "SCBUTCP.DEF", "SCBUTT3.DEF", "SCBUTT4.DEF", "SCBUTT5.DEF"};
@@ -177,7 +178,7 @@ SelectionTab::SelectionTab(ESelectionScreen Type)
 			if(criteria == _name)
 				criteria = generalSortingBy;
 
-			buttonsSortBy.push_back(std::make_shared<CButton>(Point(xpos[i], 86), sortIconNames[i], CGI->generaltexth->zelp[107 + i], std::bind(&SelectionTab::sortBy, this, criteria)));
+			buttonsSortBy.push_back(std::make_shared<CButton>(Point(xpos[i], 86), AnimationPath::builtin(sortIconNames[i]), CGI->generaltexth->zelp[107 + i], std::bind(&SelectionTab::sortBy, this, criteria)));
 		}
 	}
 
@@ -203,17 +204,17 @@ SelectionTab::SelectionTab(ESelectionScreen Type)
 		pos.x += 3;
 		pos.y += 6;
 
-		buttonsSortBy.push_back(std::make_shared<CButton>(Point(23, 86), "CamCusM.DEF", CButton::tooltip(), std::bind(&SelectionTab::sortBy, this, _numOfMaps)));
-		buttonsSortBy.push_back(std::make_shared<CButton>(Point(55, 86), "CamCusL.DEF", CButton::tooltip(), std::bind(&SelectionTab::sortBy, this, _name)));
+		buttonsSortBy.push_back(std::make_shared<CButton>(Point(23, 86), AnimationPath::builtin("CamCusM.DEF"), CButton::tooltip(), std::bind(&SelectionTab::sortBy, this, _numOfMaps)));
+		buttonsSortBy.push_back(std::make_shared<CButton>(Point(55, 86), AnimationPath::builtin("CamCusL.DEF"), CButton::tooltip(), std::bind(&SelectionTab::sortBy, this, _name)));
 		break;
 	default:
 		assert(0);
 		break;
 	}
 
-	iconsMapFormats = std::make_shared<CAnimation>("SCSELC.DEF");
-	iconsVictoryCondition = std::make_shared<CAnimation>("SCNRVICT.DEF");
-	iconsLossCondition = std::make_shared<CAnimation>("SCNRLOSS.DEF");
+	iconsMapFormats = GH.renderHandler().loadAnimation(AnimationPath::builtin("SCSELC.DEF"));
+	iconsVictoryCondition = GH.renderHandler().loadAnimation(AnimationPath::builtin("SCNRVICT.DEF"));
+	iconsLossCondition = GH.renderHandler().loadAnimation(AnimationPath::builtin("SCNRLOSS.DEF"));
 	for(int i = 0; i < positionsToShow; i++)
 		listItems.push_back(std::make_shared<ListItem>(Point(30, 129 + i * 25), iconsMapFormats, iconsVictoryCondition, iconsLossCondition));
 
@@ -244,7 +245,7 @@ void SelectionTab::toggleMode()
 			{
 				inputName->disable();
 				auto files = getFiles("Maps/", EResType::MAP);
-				files.erase(ResourceID("Maps/Tutorial.tut", EResType::MAP));
+				files.erase(ResourcePath("Maps/Tutorial.tut", EResType::MAP));
 				parseMaps(files);
 				break;
 			}
@@ -367,7 +368,7 @@ void SelectionTab::showPopupWindow(const Point & cursorPosition)
 		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);
 
-		GH.windows().createAndPushWindow<CMapInfoTooltipBox>(text, ResourceID(curItems[py]->fileURI), tabType);
+		GH.windows().createAndPushWindow<CMapInfoTooltipBox>(text, ResourcePath(curItems[py]->fileURI), tabType);
 	}
 }
 
@@ -556,7 +557,7 @@ void SelectionTab::select(int position)
 
 	if(inputName && inputName->isActive())
 	{
-		auto filename = *CResourceHandler::get()->getResourceName(ResourceID(curItems[py]->fileURI, EResType::SAVEGAME));
+		auto filename = *CResourceHandler::get()->getResourceName(ResourcePath(curItems[py]->fileURI, EResType::SAVEGAME));
 		inputName->setText(filename.stem().string());
 	}
 
@@ -723,7 +724,7 @@ bool SelectionTab::isMapSupported(const CMapInfo & info)
 	return false;
 }
 
-void SelectionTab::parseMaps(const std::unordered_set<ResourceID> & files)
+void SelectionTab::parseMaps(const std::unordered_set<ResourcePath> & files)
 {
 	logGlobal->debug("Parsing %d maps", files.size());
 	allItems.clear();
@@ -744,7 +745,7 @@ void SelectionTab::parseMaps(const std::unordered_set<ResourceID> & files)
 	}
 }
 
-void SelectionTab::parseSaves(const std::unordered_set<ResourceID> & files)
+void SelectionTab::parseSaves(const std::unordered_set<ResourcePath> & files)
 {
 	for(auto & file : files)
 	{
@@ -786,7 +787,7 @@ void SelectionTab::parseSaves(const std::unordered_set<ResourceID> & files)
 	}
 }
 
-void SelectionTab::parseCampaigns(const std::unordered_set<ResourceID> & files)
+void SelectionTab::parseCampaigns(const std::unordered_set<ResourcePath> & files)
 {
 	allItems.reserve(files.size());
 	for(auto & file : files)
@@ -800,7 +801,7 @@ void SelectionTab::parseCampaigns(const std::unordered_set<ResourceID> & files)
 	}
 }
 
-std::unordered_set<ResourceID> SelectionTab::getFiles(std::string dirURI, int resType)
+std::unordered_set<ResourcePath> SelectionTab::getFiles(std::string dirURI, EResType resType)
 {
 	boost::to_upper(dirURI);
 	CResourceHandler::get()->updateFilteredFiles([&](const std::string & mount)
@@ -808,7 +809,7 @@ std::unordered_set<ResourceID> SelectionTab::getFiles(std::string dirURI, int re
 		return boost::algorithm::starts_with(mount, dirURI);
 	});
 
-	std::unordered_set<ResourceID> ret = CResourceHandler::get()->getFilteredFiles([&](const ResourceID & ident)
+	std::unordered_set<ResourcePath> ret = CResourceHandler::get()->getFilteredFiles([&](const ResourcePath & ident)
 	{
 		return ident.getType() == resType && boost::algorithm::starts_with(ident.getName(), dirURI);
 	});
@@ -816,7 +817,7 @@ std::unordered_set<ResourceID> SelectionTab::getFiles(std::string dirURI, int re
 	return ret;
 }
 
-SelectionTab::CMapInfoTooltipBox::CMapInfoTooltipBox(std::string text, ResourceID resource, ESelectionScreen tabType)
+SelectionTab::CMapInfoTooltipBox::CMapInfoTooltipBox(std::string text, ResourcePath resource, ESelectionScreen tabType)
 	: CWindowObject(BORDERED | RCLICK_POPUP)
 {
 	drawPlayerElements = tabType == ESelectionScreen::newGame;
@@ -826,7 +827,7 @@ SelectionTab::CMapInfoTooltipBox::CMapInfoTooltipBox(std::string text, ResourceI
 
 	std::vector<std::shared_ptr<IImage>> mapLayerImages;
 	if(renderImage)
-		mapLayerImages = createMinimaps(ResourceID(resource.getName(), EResType::MAP), IMAGE_SIZE);
+		mapLayerImages = createMinimaps(ResourcePath(resource.getName(), EResType::MAP), IMAGE_SIZE);
 
 	if(mapLayerImages.size() == 0)
 		renderImage = false;
@@ -844,7 +845,7 @@ SelectionTab::CMapInfoTooltipBox::CMapInfoTooltipBox(std::string text, ResourceI
 	pos.h = BORDER + textHeight + BORDER;
 	if(renderImage)
 		pos.h += IMAGE_SIZE + BORDER;
-	backgroundTexture = std::make_shared<CFilledTexture>("DIBOXBCK", pos);
+	backgroundTexture = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), pos);
 	updateShadow();
 
 	drawLabel();
@@ -903,7 +904,7 @@ Canvas SelectionTab::CMapInfoTooltipBox::createMinimapForLayer(std::unique_ptr<C
 	return canvas;
 }
 
-std::vector<std::shared_ptr<IImage>> SelectionTab::CMapInfoTooltipBox::createMinimaps(ResourceID resource, int size)
+std::vector<std::shared_ptr<IImage>> SelectionTab::CMapInfoTooltipBox::createMinimaps(ResourcePath resource, int size)
 {
 	std::vector<std::shared_ptr<IImage>> ret = std::vector<std::shared_ptr<IImage>>();
 
@@ -923,7 +924,7 @@ std::vector<std::shared_ptr<IImage>> SelectionTab::CMapInfoTooltipBox::createMin
 		Canvas canvas = createMinimapForLayer(map, i);
 		Canvas canvasScaled = Canvas(Point(size, size));
 		canvasScaled.drawScaled(canvas, Point(0, 0), Point(size, size));
-		std::shared_ptr<IImage> img = IImage::createFromSurface(canvasScaled.getInternalSurface());
+		std::shared_ptr<IImage> img = GH.renderHandler().createImage(canvasScaled.getInternalSurface());
 		
 		ret.push_back(img);
 	}
@@ -935,7 +936,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);
+	pictureEmptyLine = std::make_shared<CPicture>(GH.renderHandler().loadImage(ImagePath::builtin("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);
@@ -945,7 +946,7 @@ 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);
+	iconFolder = std::make_shared<CPicture>(ImagePath::builtin("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);

+ 7 - 6
client/lobby/SelectionTab.h

@@ -14,6 +14,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 class CMap;
 VCMI_LIB_NAMESPACE_END
 #include "../../lib/mapping/CMapInfo.h"
+#include "../../lib/filesystem/ResourcePath.h"
 
 class CSlider;
 class CLabel;
@@ -81,9 +82,9 @@ class SelectionTab : public CIntObject
 		std::shared_ptr<CPicture> image2;
 
 		Canvas createMinimapForLayer(std::unique_ptr<CMap> & map, int layer);
-		std::vector<std::shared_ptr<IImage>> createMinimaps(ResourceID resource, int size);
+		std::vector<std::shared_ptr<IImage>> createMinimaps(ResourcePath resource, int size);
 	public:
-		CMapInfoTooltipBox(std::string text, ResourceID resource, ESelectionScreen tabType);
+		CMapInfoTooltipBox(std::string text, ResourcePath resource, ESelectionScreen tabType);
 	};
 public:
 	std::vector<std::shared_ptr<ElementInfo>> allItems;
@@ -134,8 +135,8 @@ private:
 	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);
-	void parseCampaigns(const std::unordered_set<ResourceID> & files);
-	std::unordered_set<ResourceID> getFiles(std::string dirURI, int resType);
+	void parseMaps(const std::unordered_set<ResourcePath> & files);
+	void parseSaves(const std::unordered_set<ResourcePath> & files);
+	void parseCampaigns(const std::unordered_set<ResourcePath> & files);
+	std::unordered_set<ResourcePath> getFiles(std::string dirURI, EResType resType);
 };

+ 10 - 15
client/mainmenu/CCampaignScreen.cpp

@@ -75,7 +75,7 @@ CCampaignScreen::CCampaignScreen(const JsonNode & config)
 
 void CCampaignScreen::activate()
 {
-	CCS->musich->playMusic("Music/MainMenu", true, false);
+	CCS->musich->playMusic(AudioPath::builtin("Music/MainMenu"), true, false);
 
 	CWindowObject::activate();
 }
@@ -86,7 +86,7 @@ std::shared_ptr<CButton> CCampaignScreen::createExitButton(const JsonNode & butt
 	if(!button["help"].isNull() && button["help"].Float() > 0)
 		help = CGI->generaltexth->zelp[(size_t)button["help"].Float()];
 
-	return std::make_shared<CButton>(Point((int)button["x"].Float(), (int)button["y"].Float()), button["name"].String(), help, [=](){ close();}, EShortcut::GLOBAL_CANCEL);
+	return std::make_shared<CButton>(Point((int)button["x"].Float(), (int)button["y"].Float()), AnimationPath::fromJson(button["name"]), help, [=](){ close();}, EShortcut::GLOBAL_CANCEL);
 }
 
 CCampaignScreen::CCampaignButton::CCampaignButton(const JsonNode & config)
@@ -99,7 +99,7 @@ CCampaignScreen::CCampaignButton::CCampaignButton(const JsonNode & config)
 	pos.h = 116;
 
 	campFile = config["file"].String();
-	video = config["video"].String();
+	video = VideoPath::fromJson(config["video"]);
 
 	status = config["open"].Bool() ? CCampaignScreen::ENABLED : CCampaignScreen::DISABLED;
 
@@ -109,14 +109,14 @@ CCampaignScreen::CCampaignButton::CCampaignButton(const JsonNode & config)
 	if(status != CCampaignScreen::DISABLED)
 	{
 		addUsedEvents(LCLICK | HOVER);
-		graphicsImage = std::make_shared<CPicture>(config["image"].String());
+		graphicsImage = std::make_shared<CPicture>(ImagePath::fromJson(config["image"]));
 
 		hoverLabel = std::make_shared<CLabel>(pos.w / 2, pos.h + 20, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, "");
 		parent->addChild(hoverLabel.get());
 	}
 
 	if(status == CCampaignScreen::COMPLETED)
-		graphicsCompleted = std::make_shared<CPicture>("CAMPCHK");
+		graphicsCompleted = std::make_shared<CPicture>(ImagePath::builtin("CAMPCHK"));
 }
 
 void CCampaignScreen::CCampaignButton::show(Canvas & to)
@@ -128,17 +128,7 @@ void CCampaignScreen::CCampaignButton::show(Canvas & to)
 
 	// Play the campaign button video when the mouse cursor is placed over the button
 	if(isHovered())
-	{
-		if(CCS->videoh->fname != video)
-			CCS->videoh->open(video);
-
 		CCS->videoh->update(pos.x, pos.y, to.getInternalSurface(), true, false); // plays sequentially frame by frame, starts at the beginning when the video is over
-	}
-	else if(CCS->videoh->fname == video) // When you got out of the bounds of the button then close the video
-	{
-		CCS->videoh->close();
-		redraw();
-	}
 }
 
 void CCampaignScreen::CCampaignButton::clickReleased(const Point & cursorPosition)
@@ -149,6 +139,11 @@ void CCampaignScreen::CCampaignButton::clickReleased(const Point & cursorPositio
 
 void CCampaignScreen::CCampaignButton::hover(bool on)
 {
+	if (on)
+		CCS->videoh->open(video);
+	else
+		CCS->videoh->close();
+
 	if(hoverLabel)
 	{
 		if(on)

+ 1 - 1
client/mainmenu/CCampaignScreen.h

@@ -37,7 +37,7 @@ private:
 		CampaignStatus status;
 
 		std::string campFile; // the filename/resourcename of the campaign
-		std::string video; // the resource name of the video
+		VideoPath video; // the resource name of the video
 		std::string hoverText;
 
 		void clickReleased(const Point & cursorPosition) override;

+ 27 - 26
client/mainmenu/CMainMenu.cpp

@@ -78,7 +78,7 @@ CMenuScreen::CMenuScreen(const JsonNode & configNode)
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
 
-	background = std::make_shared<CPicture>(config["background"].String());
+	background = std::make_shared<CPicture>(ImagePath::fromJson(config["background"]));
 	if(config["scalable"].Bool())
 		background->scaleTo(GH.screenDimensions());
 
@@ -114,9 +114,9 @@ void CMenuScreen::show(Canvas & to)
 
 void CMenuScreen::activate()
 {
-	CCS->musich->playMusic("Music/MainMenu", true, true);
+	CCS->musich->playMusic(AudioPath::builtin("Music/MainMenu"), true, true);
 	if(!config["video"].isNull())
-		CCS->videoh->open(config["video"]["name"].String());
+		CCS->videoh->open(VideoPath::fromJson(config["video"]["name"]));
 	CIntObject::activate();
 }
 
@@ -237,7 +237,7 @@ std::shared_ptr<CButton> CMenuEntry::createButton(CMenuScreen * parent, const Js
 
 	EShortcut shortcut = GH.shortcuts().findShortcut(button["shortcut"].String());
 
-	auto result = std::make_shared<CButton>(Point(posx, posy), button["name"].String(), help, command, shortcut);
+	auto result = std::make_shared<CButton>(Point(posx, posy), AnimationPath::fromJson(button["name"]), help, command, shortcut);
 
 	if (button["center"].Bool())
 		result->moveBy(Point(-result->pos.w/2, -result->pos.h/2));
@@ -262,7 +262,8 @@ CMenuEntry::CMenuEntry(CMenuScreen * parent, const JsonNode & config)
 }
 
 CMainMenuConfig::CMainMenuConfig()
-	: campaignSets(JsonNode(ResourceID("config/campaignSets.json"))), config(JsonNode(ResourceID("config/mainmenu.json")))
+	: campaignSets(JsonPath::builtin("config/campaignSets.json"))
+	, config(JsonPath::builtin("config/mainmenu.json"))
 {
 
 }
@@ -291,7 +292,7 @@ CMainMenu::CMainMenu()
 	GH.defActionsDef = 63;
 	menu = std::make_shared<CMenuScreen>(CMainMenuConfig::get().getConfig()["window"]);
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
-	backgroundAroundMenu = std::make_shared<CFilledTexture>("DIBOXBCK", pos);
+	backgroundAroundMenu = std::make_shared<CFilledTexture>(ImagePath::builtin("DIBOXBCK"), pos);
 }
 
 CMainMenu::~CMainMenu()
@@ -374,7 +375,7 @@ void CMainMenu::openCampaignScreen(std::string name)
 
 void CMainMenu::startTutorial()
 {
-	ResourceID tutorialMap("Maps/Tutorial.tut", EResType::MAP);
+	ResourcePath tutorialMap("Maps/Tutorial.tut", EResType::MAP);
 	if(!CResourceHandler::get()->existsResource(tutorialMap))
 	{
 		CInfoWindow::showInfoDialog(CGI->generaltexth->translate("core.genrltxt.742"), std::vector<std::shared_ptr<CComponent>>(), PlayerColor(1));
@@ -397,7 +398,7 @@ std::shared_ptr<CMainMenu> CMainMenu::create()
 
 std::shared_ptr<CPicture> CMainMenu::createPicture(const JsonNode & config)
 {
-	return std::make_shared<CPicture>(config["name"].String(), (int)config["x"].Float(), (int)config["y"].Float());
+	return std::make_shared<CPicture>(ImagePath::fromJson(config["name"]), (int)config["x"].Float(), (int)config["y"].Float());
 }
 
 CMultiMode::CMultiMode(ESelectionScreen ScreenType)
@@ -405,20 +406,20 @@ CMultiMode::CMultiMode(ESelectionScreen ScreenType)
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
 
-	background = std::make_shared<CPicture>("MUPOPUP.bmp");
+	background = std::make_shared<CPicture>(ImagePath::builtin("MUPOPUP.bmp"));
 	pos = background->center(); //center, window has size of bg graphic
 
-	picture = std::make_shared<CPicture>("MUMAP.bmp", 16, 77);
+	picture = std::make_shared<CPicture>(ImagePath::builtin("MUMAP.bmp"), 16, 77);
 
 	statusBar = CGStatusBar::create(std::make_shared<CPicture>(background->getSurface(), Rect(7, 465, 440, 18), 7, 465));
 	playerName = std::make_shared<CTextInput>(Rect(19, 436, 334, 16), background->getSurface());
 	playerName->setText(getPlayerName());
 	playerName->cb += std::bind(&CMultiMode::onNameChange, this, _1);
 
-	buttonHotseat = std::make_shared<CButton>(Point(373, 78), "MUBHOT.DEF", CGI->generaltexth->zelp[266], std::bind(&CMultiMode::hostTCP, this));
-	buttonHost = std::make_shared<CButton>(Point(373, 78 + 57 * 1), "MUBHOST.DEF", CButton::tooltip(CGI->generaltexth->translate("vcmi.mainMenu.hostTCP"), ""), std::bind(&CMultiMode::hostTCP, this));
-	buttonJoin = std::make_shared<CButton>(Point(373, 78 + 57 * 2), "MUBJOIN.DEF", CButton::tooltip(CGI->generaltexth->translate("vcmi.mainMenu.joinTCP"), ""), std::bind(&CMultiMode::joinTCP, this));
-	buttonCancel = std::make_shared<CButton>(Point(373, 424), "MUBCANC.DEF", CGI->generaltexth->zelp[288], [=](){ close();}, EShortcut::GLOBAL_CANCEL);
+	buttonHotseat = std::make_shared<CButton>(Point(373, 78), AnimationPath::builtin("MUBHOT.DEF"), CGI->generaltexth->zelp[266], std::bind(&CMultiMode::hostTCP, this));
+	buttonHost = std::make_shared<CButton>(Point(373, 78 + 57 * 1), AnimationPath::builtin("MUBHOST.DEF"), CButton::tooltip(CGI->generaltexth->translate("vcmi.mainMenu.hostTCP"), ""), std::bind(&CMultiMode::hostTCP, this));
+	buttonJoin = std::make_shared<CButton>(Point(373, 78 + 57 * 2), AnimationPath::builtin("MUBJOIN.DEF"), CButton::tooltip(CGI->generaltexth->translate("vcmi.mainMenu.joinTCP"), ""), std::bind(&CMultiMode::joinTCP, this));
+	buttonCancel = std::make_shared<CButton>(Point(373, 424), AnimationPath::builtin("MUBCANC.DEF"), CGI->generaltexth->zelp[288], [=](){ close();}, EShortcut::GLOBAL_CANCEL);
 }
 
 void CMultiMode::hostTCP()
@@ -453,7 +454,7 @@ CMultiPlayers::CMultiPlayers(const std::string & firstPlayer, ESelectionScreen S
 	: loadMode(LoadMode), screenType(ScreenType), host(Host)
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
-	background = std::make_shared<CPicture>("MUHOTSEA.bmp");
+	background = std::make_shared<CPicture>(ImagePath::builtin("MUHOTSEA.bmp"));
 	pos = background->center(); //center, window has size of bg graphic
 
 	std::string text = CGI->generaltexth->allTexts[446];
@@ -466,8 +467,8 @@ CMultiPlayers::CMultiPlayers(const std::string & firstPlayer, ESelectionScreen S
 		inputNames[i]->cb += std::bind(&CMultiPlayers::onChange, this, _1);
 	}
 
-	buttonOk = std::make_shared<CButton>(Point(95, 338), "MUBCHCK.DEF", CGI->generaltexth->zelp[560], std::bind(&CMultiPlayers::enterSelectionScreen, this), EShortcut::GLOBAL_ACCEPT);
-	buttonCancel = std::make_shared<CButton>(Point(205, 338), "MUBCANC.DEF", CGI->generaltexth->zelp[561], [=](){ close();}, EShortcut::GLOBAL_CANCEL);
+	buttonOk = std::make_shared<CButton>(Point(95, 338), AnimationPath::builtin("MUBCHCK.DEF"), CGI->generaltexth->zelp[560], std::bind(&CMultiPlayers::enterSelectionScreen, this), EShortcut::GLOBAL_ACCEPT);
+	buttonCancel = std::make_shared<CButton>(Point(205, 338), AnimationPath::builtin("MUBCANC.DEF"), CGI->generaltexth->zelp[561], [=](){ close();}, EShortcut::GLOBAL_CANCEL);
 	statusBar = CGStatusBar::create(std::make_shared<CPicture>(background->getSurface(), Rect(7, 381, 348, 18), 7, 381));
 
 	inputNames[0]->setText(firstPlayer, true);
@@ -498,7 +499,7 @@ void CMultiPlayers::enterSelectionScreen()
 CSimpleJoinScreen::CSimpleJoinScreen(bool host)
 {
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
-	background = std::make_shared<CPicture>("MUDIALOG.bmp"); // address background
+	background = std::make_shared<CPicture>(ImagePath::builtin("MUDIALOG.bmp")); // address background
 	pos = background->center(); //center, window has size of bg graphic (x,y = 396,278 w=232 h=212)
 
 	textTitle = std::make_shared<CTextBox>("", Rect(20, 20, 205, 50), 0, FONT_BIG, ETextAlignment::CENTER, Colors::WHITE);
@@ -515,14 +516,14 @@ CSimpleJoinScreen::CSimpleJoinScreen(bool host)
 		inputAddress->cb += std::bind(&CSimpleJoinScreen::onChange, this, _1);
 		inputPort->cb += std::bind(&CSimpleJoinScreen::onChange, this, _1);
 		inputPort->filters += std::bind(&CTextInput::numberFilter, _1, _2, 0, 65535);
-		buttonOk = std::make_shared<CButton>(Point(26, 142), "MUBCHCK.DEF", CGI->generaltexth->zelp[560], std::bind(&CSimpleJoinScreen::connectToServer, this), EShortcut::GLOBAL_ACCEPT);
+		buttonOk = std::make_shared<CButton>(Point(26, 142), AnimationPath::builtin("MUBCHCK.DEF"), CGI->generaltexth->zelp[560], std::bind(&CSimpleJoinScreen::connectToServer, this), EShortcut::GLOBAL_ACCEPT);
 
 		inputAddress->giveFocus();
 	}
 	inputAddress->setText(host ? CServerHandler::localhostAddress : CSH->getHostAddress(), true);
 	inputPort->setText(std::to_string(CSH->getHostPort()), true);
 
-	buttonCancel = std::make_shared<CButton>(Point(142, 142), "MUBCANC.DEF", CGI->generaltexth->zelp[561], std::bind(&CSimpleJoinScreen::leaveScreen, this), EShortcut::GLOBAL_CANCEL);
+	buttonCancel = std::make_shared<CButton>(Point(142, 142), AnimationPath::builtin("MUBCANC.DEF"), CGI->generaltexth->zelp[561], std::bind(&CSimpleJoinScreen::leaveScreen, this), EShortcut::GLOBAL_CANCEL);
 	statusBar = CGStatusBar::create(std::make_shared<CPicture>(background->getSurface(), Rect(7, 186, 218, 18), 7, 186));
 }
 
@@ -601,7 +602,7 @@ CLoadingScreen::CLoadingScreen()
 		
 		for(int i = 0; i < blocksAmount; ++i)
 		{
-			progressBlocks.push_back(std::make_shared<CAnimImage>(conf["name"].String(), i, 0, posx + i * blockSize, posy));
+			progressBlocks.push_back(std::make_shared<CAnimImage>(AnimationPath::fromJson(conf["name"]), i, 0, posx + i * blockSize, posy));
 			progressBlocks.back()->deactivate();
 			progressBlocks.back()->visible = false;
 		}
@@ -626,24 +627,24 @@ void CLoadingScreen::tick(uint32_t msPassed)
 	}
 }
 
-std::string CLoadingScreen::getBackground()
+ImagePath CLoadingScreen::getBackground()
 {
-	std::string fname = "loadbar";
+	ImagePath fname = ImagePath::builtin("loadbar");
 	const auto & conf = CMainMenuConfig::get().getConfig()["loading"];
 
 	if(conf.isStruct())
 	{
 		if(conf["background"].isVector())
-			return RandomGeneratorUtil::nextItem(conf["background"].Vector(), CRandomGenerator::getDefault())->String();
+			return ImagePath::fromJson(*RandomGeneratorUtil::nextItem(conf["background"].Vector(), CRandomGenerator::getDefault()));
 		
 		if(conf["background"].isString())
-			return conf["background"].String();
+			return ImagePath::fromJson(conf["background"]);
 		
 		return fname;
 	}
 	
 	if(conf.isVector() && !conf.Vector().empty())
-		return RandomGeneratorUtil::nextItem(conf.Vector(), CRandomGenerator::getDefault())->String();
+		return ImagePath::fromJson(*RandomGeneratorUtil::nextItem(conf.Vector(), CRandomGenerator::getDefault()));
 	
 	return fname;
 }

+ 1 - 1
client/mainmenu/CMainMenu.h

@@ -185,7 +185,7 @@ class CLoadingScreen : virtual public CWindowObject, virtual public Load::Progre
 {
 	std::vector<std::shared_ptr<CAnimImage>> progressBlocks;
 	
-	std::string getBackground();
+	ImagePath getBackground();
 
 public:	
 	CLoadingScreen();

+ 1 - 1
client/mainmenu/CPrologEpilogVideo.cpp

@@ -28,7 +28,7 @@ CPrologEpilogVideo::CPrologEpilogVideo(CampaignScenarioPrologEpilog _spe, std::f
 	updateShadow();
 
 	CCS->videoh->open(spe.prologVideo);
-	CCS->musich->playMusic("Music/" + spe.prologMusic, true, true);
+	CCS->musich->playMusic(spe.prologMusic, true, true);
 	// MPTODO: Custom campaign crashing on this?
 //	voiceSoundHandle = CCS->soundh->playSound(CCampaignHandler::prologVoiceName(spe.prologVideo));
 

+ 1 - 1
client/mainmenu/CreditsScreen.cpp

@@ -26,7 +26,7 @@ CreditsScreen::CreditsScreen(Rect rect)
 	pos.h = rect.h;
 	setRedrawParent(true);
 	OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
-	auto textFile = CResourceHandler::get()->load(ResourceID("DATA/CREDITS.TXT"))->readAll();
+	auto textFile = CResourceHandler::get()->load(ResourcePath("DATA/CREDITS.TXT"))->readAll();
 	std::string text((char *)textFile.first.get(), textFile.second);
 	size_t firstQuote = text.find('\"') + 1;
 	text = text.substr(firstQuote, text.find('\"', firstQuote) - firstQuote);

+ 16 - 14
client/mapView/MapRenderer.cpp

@@ -15,9 +15,11 @@
 #include "mapHandler.h"
 
 #include "../CGameInfo.h"
+#include "../gui/CGuiHandler.h"
 #include "../render/CAnimation.h"
 #include "../render/Canvas.h"
 #include "../render/IImage.h"
+#include "../render/IRenderHandler.h"
 
 #include "../../CCallback.h"
 
@@ -94,7 +96,7 @@ MapTileStorage::MapTileStorage(size_t capacity)
 {
 }
 
-void MapTileStorage::load(size_t index, const std::string & filename, EImageBlitMode blitMode)
+void MapTileStorage::load(size_t index, const AnimationPath & filename, EImageBlitMode blitMode)
 {
 	auto & terrainAnimations = animations[index];
 
@@ -102,11 +104,11 @@ void MapTileStorage::load(size_t index, const std::string & filename, EImageBlit
 	{
 		if (!filename.empty())
 		{
-			entry = std::make_unique<CAnimation>(filename);
+			entry = GH.renderHandler().loadAnimation(filename);
 			entry->preload();
 		}
 		else
-			entry = std::make_unique<CAnimation>();
+			entry = GH.renderHandler().createAnimation();
 
 		for(size_t i = 0; i < entry->size(); ++i)
 			entry->getImage(i)->setBlitMode(blitMode);
@@ -247,7 +249,7 @@ uint8_t MapRendererRoad::checksum(IMapRendererContext & context, const int3 & co
 MapRendererBorder::MapRendererBorder()
 {
 	emptyFill = std::make_unique<Canvas>(Point(32,32));
-	animation = std::make_unique<CAnimation>("EDG");
+	animation = GH.renderHandler().loadAnimation(AnimationPath::builtin("EDG"));
 	animation->preload();
 }
 
@@ -309,9 +311,9 @@ uint8_t MapRendererBorder::checksum(IMapRendererContext & context, const int3 &
 
 MapRendererFow::MapRendererFow()
 {
-	fogOfWarFullHide = std::make_unique<CAnimation>("TSHRC");
+	fogOfWarFullHide = GH.renderHandler().loadAnimation(AnimationPath::builtin("TSHRC"));
 	fogOfWarFullHide->preload();
-	fogOfWarPartialHide = std::make_unique<CAnimation>("TSHRE");
+	fogOfWarPartialHide = GH.renderHandler().loadAnimation(AnimationPath::builtin("TSHRE"));
 	fogOfWarPartialHide->preload();
 
 	for(size_t i = 0; i < fogOfWarFullHide->size(); ++i)
@@ -387,14 +389,14 @@ std::shared_ptr<CAnimation> MapRendererObjects::getBaseAnimation(const CGObjectI
 	return getAnimation(info->animationFile, generateMovementGroups);
 }
 
-std::shared_ptr<CAnimation> MapRendererObjects::getAnimation(const std::string & filename, bool generateMovementGroups)
+std::shared_ptr<CAnimation> MapRendererObjects::getAnimation(const AnimationPath & filename, bool generateMovementGroups)
 {
 	auto it = animations.find(filename);
 
 	if(it != animations.end())
 		return it->second;
 
-	auto ret = std::make_shared<CAnimation>(filename);
+	auto ret = GH.renderHandler().loadAnimation(filename);
 	animations[filename] = ret;
 	ret->preload();
 
@@ -422,7 +424,7 @@ std::shared_ptr<CAnimation> MapRendererObjects::getFlagAnimation(const CGObjectI
 	{
 		assert(dynamic_cast<const CGHeroInstance *>(obj) != nullptr);
 		assert(obj->tempOwner.isValidPlayer());
-		return getAnimation(heroFlags[obj->tempOwner.getNum()], true);
+		return getAnimation(AnimationPath::builtin(heroFlags[obj->tempOwner.getNum()]), true);
 	}
 
 	if(obj->ID == Obj::BOAT)
@@ -557,10 +559,10 @@ uint8_t MapRendererObjects::checksum(IMapRendererContext & context, const int3 &
 }
 
 MapRendererOverlay::MapRendererOverlay()
-	: imageGrid(IImage::createFromFile("debug/grid", EImageBlitMode::ALPHA))
-	, imageBlocked(IImage::createFromFile("debug/blocked", EImageBlitMode::ALPHA))
-	, imageVisitable(IImage::createFromFile("debug/visitable", EImageBlitMode::ALPHA))
-	, imageSpellRange(IImage::createFromFile("debug/spellRange", EImageBlitMode::ALPHA))
+	: imageGrid(GH.renderHandler().loadImage(ImagePath::builtin("debug/grid"), EImageBlitMode::ALPHA))
+	, imageBlocked(GH.renderHandler().loadImage(ImagePath::builtin("debug/blocked"), EImageBlitMode::ALPHA))
+	, imageVisitable(GH.renderHandler().loadImage(ImagePath::builtin("debug/visitable"), EImageBlitMode::ALPHA))
+	, imageSpellRange(GH.renderHandler().loadImage(ImagePath::builtin("debug/spellRange"), EImageBlitMode::ALPHA))
 {
 
 }
@@ -616,7 +618,7 @@ uint8_t MapRendererOverlay::checksum(IMapRendererContext & context, const int3 &
 }
 
 MapRendererPath::MapRendererPath()
-	: pathNodes(new CAnimation("ADAG"))
+	: pathNodes(GH.renderHandler().loadAnimation(AnimationPath::builtin("ADAG")))
 {
 	pathNodes->preload();
 }

+ 11 - 9
client/mapView/MapRenderer.h

@@ -9,6 +9,8 @@
  */
 #pragma once
 
+#include "../../lib/filesystem/ResourcePath.h"
+
 VCMI_LIB_NAMESPACE_BEGIN
 
 class int3;
@@ -21,16 +23,16 @@ class CAnimation;
 class IImage;
 class Canvas;
 class IMapRendererContext;
-enum class EImageBlitMode : uint8_t;
+enum class EImageBlitMode;
 
 class MapTileStorage
 {
-	using TerrainAnimation = std::array<std::unique_ptr<CAnimation>, 4>;
+	using TerrainAnimation = std::array<std::shared_ptr<CAnimation>, 4>;
 	std::vector<TerrainAnimation> animations;
 
 public:
 	explicit MapTileStorage(size_t capacity);
-	void load(size_t index, const std::string & filename, EImageBlitMode blitMode);
+	void load(size_t index, const AnimationPath & filename, EImageBlitMode blitMode);
 	std::shared_ptr<IImage> find(size_t fileIndex, size_t rotationIndex, size_t imageIndex);
 };
 
@@ -69,13 +71,13 @@ public:
 
 class MapRendererObjects
 {
-	std::unordered_map<std::string, std::shared_ptr<CAnimation>> animations;
+	std::map<AnimationPath, std::shared_ptr<CAnimation>> animations;
 
 	std::shared_ptr<CAnimation> getBaseAnimation(const CGObjectInstance * obj);
 	std::shared_ptr<CAnimation> getFlagAnimation(const CGObjectInstance * obj);
 	std::shared_ptr<CAnimation> getOverlayAnimation(const CGObjectInstance * obj);
 
-	std::shared_ptr<CAnimation> getAnimation(const std::string & filename, bool generateMovementGroups);
+	std::shared_ptr<CAnimation> getAnimation(const AnimationPath & filename, bool generateMovementGroups);
 
 	std::shared_ptr<IImage> getImage(IMapRendererContext & context, const CGObjectInstance * obj, const std::shared_ptr<CAnimation> & animation) const;
 
@@ -89,7 +91,7 @@ public:
 
 class MapRendererBorder
 {
-	std::unique_ptr<CAnimation> animation;
+	std::shared_ptr<CAnimation> animation;
 	std::unique_ptr<Canvas> emptyFill;
 
 	size_t getIndexForTile(IMapRendererContext & context, const int3 & coordinates);
@@ -103,8 +105,8 @@ public:
 
 class MapRendererFow
 {
-	std::unique_ptr<CAnimation> fogOfWarFullHide;
-	std::unique_ptr<CAnimation> fogOfWarPartialHide;
+	std::shared_ptr<CAnimation> fogOfWarFullHide;
+	std::shared_ptr<CAnimation> fogOfWarPartialHide;
 
 public:
 	MapRendererFow();
@@ -115,7 +117,7 @@ public:
 
 class MapRendererPath
 {
-	std::unique_ptr<CAnimation> pathNodes;
+	std::shared_ptr<CAnimation> pathNodes;
 
 	size_t selectImageReachability(bool reachableToday, size_t imageIndex);
 	size_t selectImageCross(bool reachableToday, const int3 & curr);

+ 4 - 1
client/mapView/MapViewCache.cpp

@@ -18,6 +18,9 @@
 #include "../render/CAnimation.h"
 #include "../render/Canvas.h"
 #include "../render/IImage.h"
+#include "../render/IRenderHandler.h"
+
+#include "../gui/CGuiHandler.h"
 
 #include "../../lib/mapObjects/CObjectHandler.h"
 #include "../../lib/int3.h"
@@ -28,7 +31,7 @@ MapViewCache::MapViewCache(const std::shared_ptr<MapViewModel> & model)
 	: model(model)
 	, cachedLevel(0)
 	, mapRenderer(new MapRenderer())
-	, iconsStorage(new CAnimation("VwSymbol"))
+	, iconsStorage(GH.renderHandler().loadAnimation(AnimationPath::builtin("VwSymbol")))
 	, intermediate(new Canvas(Point(32, 32)))
 	, terrain(new Canvas(model->getCacheDimensionsPixels()))
 	, terrainTransition(new Canvas(model->getPixelsVisibleDimensions()))

+ 1 - 1
client/mapView/MapViewCache.h

@@ -52,7 +52,7 @@ class MapViewCache
 	std::unique_ptr<Canvas> intermediate;
 	std::unique_ptr<MapRenderer> mapRenderer;
 
-	std::unique_ptr<CAnimation> iconsStorage;
+	std::shared_ptr<CAnimation> iconsStorage;
 
 	Canvas getTile(const int3 & coordinates);
 	void updateTile(const std::shared_ptr<IMapRendererContext> & context, const int3 & coordinates);

+ 17 - 27
client/render/CAnimation.cpp

@@ -22,7 +22,7 @@ std::shared_ptr<IImage> CAnimation::getFromExtraDef(std::string filename)
 	size_t pos = filename.find(':');
 	if (pos == -1)
 		return nullptr;
-	CAnimation anim(filename.substr(0, pos));
+	CAnimation anim(AnimationPath::builtinTODO(filename.substr(0, pos)));
 	pos++;
 	size_t frame = atoi(filename.c_str()+pos);
 	size_t group = 0;
@@ -69,7 +69,7 @@ bool CAnimation::loadFrame(size_t frame, size_t group)
 		// still here? image is missing
 
 		printError(frame, group, "LoadFrame");
-		images[group][frame] = std::make_shared<SDLImage>("DEFAULT", EImageBlitMode::ALPHA);
+		images[group][frame] = std::make_shared<SDLImage>(ImagePath::builtin("DEFAULT"), EImageBlitMode::ALPHA);
 	}
 	else //load from separate file
 	{
@@ -144,7 +144,7 @@ void CAnimation::exportBitmaps(const boost::filesystem::path& path) const
 		return;
 	}
 
-	boost::filesystem::path actualPath = path / "SPRITES" / name;
+	boost::filesystem::path actualPath = path / "SPRITES" / name.getName();
 	boost::filesystem::create_directories(actualPath);
 
 	size_t counter = 0;
@@ -179,16 +179,15 @@ void CAnimation::init()
 			source[defEntry.first].resize(defEntry.second);
 	}
 
-	ResourceID resID(std::string("SPRITES/") + name, EResType::TEXT);
+	if (vstd::contains(graphics->imageLists, name.getName()))
+		initFromJson(graphics->imageLists[name.getName()]);
 
-	if (vstd::contains(graphics->imageLists, resID.getName()))
-		initFromJson(graphics->imageLists[resID.getName()]);
-
-	auto configList = CResourceHandler::get()->getResourcesWithName(resID);
+	auto jsonResource = name.toType<EResType::JSON>();
+	auto configList = CResourceHandler::get()->getResourcesWithName(jsonResource);
 
 	for(auto & loader : configList)
 	{
-		auto stream = loader->load(resID);
+		auto stream = loader->load(jsonResource);
 		std::unique_ptr<ui8[]> textData(new ui8[stream->getSize()]);
 		stream->read(textData.get(), stream->getSize());
 
@@ -200,34 +199,25 @@ void CAnimation::init()
 
 void CAnimation::printError(size_t frame, size_t group, std::string type) const
 {
-	logGlobal->error("%s error: Request for frame not present in CAnimation! File name: %s, Group: %d, Frame: %d", type, name, group, frame);
+	logGlobal->error("%s error: Request for frame not present in CAnimation! File name: %s, Group: %d, Frame: %d", type, name.getOriginalName(), group, frame);
 }
 
-CAnimation::CAnimation(std::string Name):
-	name(Name),
+CAnimation::CAnimation(const AnimationPath & Name):
+	name(boost::starts_with(Name.getName(), "SPRITES") ? Name : Name.addPrefix("SPRITES/")),
 	preloaded(false),
 	defFile()
 {
-	size_t dotPos = name.find_last_of('.');
-	if ( dotPos!=-1 )
-		name.erase(dotPos);
-	std::transform(name.begin(), name.end(), name.begin(), toupper);
-
-	ResourceID resource(std::string("SPRITES/") + name, EResType::ANIMATION);
-
-	if(CResourceHandler::get()->existsResource(resource))
+	if(CResourceHandler::get()->existsResource(name))
 		defFile = std::make_shared<CDefFile>(name);
 
 	init();
 
 	if(source.empty())
-		logAnim->error("Animation %s failed to load", Name);
+		logAnim->error("Animation %s failed to load", Name.getOriginalName());
 }
 
 CAnimation::CAnimation():
-	name(""),
-	preloaded(false),
-	defFile()
+	preloaded(false)
 {
 	init();
 }
@@ -238,13 +228,13 @@ void CAnimation::duplicateImage(const size_t sourceGroup, const size_t sourceFra
 {
 	if(!source.count(sourceGroup))
 	{
-		logAnim->error("Group %d missing in %s", sourceGroup, name);
+		logAnim->error("Group %d missing in %s", sourceGroup, name.getName());
 		return;
 	}
 
 	if(source[sourceGroup].size() <= sourceFrame)
 	{
-		logAnim->error("Frame [%d %d] missing in %s", sourceGroup, sourceFrame, name);
+		logAnim->error("Frame [%d %d] missing in %s", sourceGroup, sourceFrame, name.getName());
 		return;
 	}
 
@@ -253,7 +243,7 @@ void CAnimation::duplicateImage(const size_t sourceGroup, const size_t sourceFra
 
 	if(clone.getType() == JsonNode::JsonType::DATA_NULL)
 	{
-		std::string temp =  name+":"+std::to_string(sourceGroup)+":"+std::to_string(sourceFrame);
+		std::string temp =  name.getName()+":"+std::to_string(sourceGroup)+":"+std::to_string(sourceFrame);
 		clone["file"].String() = temp;
 	}
 

+ 4 - 2
client/render/CAnimation.h

@@ -10,6 +10,7 @@
 #pragma once
 
 #include "../../lib/GameConstants.h"
+#include "../../lib/filesystem/ResourcePath.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 class JsonNode;
@@ -17,6 +18,7 @@ VCMI_LIB_NAMESPACE_END
 
 class CDefFile;
 class IImage;
+class RenderHandler;
 
 /// Class for handling animation
 class CAnimation
@@ -29,7 +31,7 @@ private:
 	std::map<size_t, std::map<size_t, std::shared_ptr<IImage> > > images;
 
 	//animation file name
-	std::string name;
+	AnimationPath name;
 
 	bool preloaded;
 
@@ -53,7 +55,7 @@ private:
 	std::shared_ptr<IImage> getFromExtraDef(std::string filename);
 
 public:
-	CAnimation(std::string Name);
+	CAnimation(const AnimationPath & Name);
 	CAnimation();
 	~CAnimation();
 

+ 24 - 19
client/render/CBitmapHandler.cpp

@@ -21,7 +21,7 @@ namespace BitmapHandler
 {
 	SDL_Surface * loadH3PCX(ui8 * data, size_t size);
 
-	SDL_Surface * loadBitmapFromDir(std::string path, std::string fname);
+	SDL_Surface * loadBitmapFromDir(const ImagePath & path);
 }
 
 bool isPCX(const ui8 *header)//check whether file can be PCX according to header
@@ -103,28 +103,23 @@ SDL_Surface * BitmapHandler::loadH3PCX(ui8 * pcx, size_t size)
 	return ret;
 }
 
-SDL_Surface * BitmapHandler::loadBitmapFromDir(std::string path, std::string fname)
+SDL_Surface * BitmapHandler::loadBitmapFromDir(const ImagePath & path)
 {
-	if(!fname.size())
-	{
-		logGlobal->warn("Call to loadBitmap with void fname!");
-		return nullptr;
-	}
-	if (!CResourceHandler::get()->existsResource(ResourceID(path + fname, EResType::IMAGE)))
+	if (!CResourceHandler::get()->existsResource(path))
 	{
 		return nullptr;
 	}
 
 	SDL_Surface * ret=nullptr;
 
-	auto readFile = CResourceHandler::get()->load(ResourceID(path + fname, EResType::IMAGE))->readAll();
+	auto readFile = CResourceHandler::get()->load(path)->readAll();
 
 	if (isPCX(readFile.first.get()))
 	{//H3-style PCX
 		ret = loadH3PCX(readFile.first.get(), readFile.second);
 		if (!ret)
 		{
-			logGlobal->error("Failed to open %s as H3 PCX!", fname);
+			logGlobal->error("Failed to open %s as H3 PCX!", path.getOriginalName());
 			return nullptr;
 		}
 	}
@@ -146,7 +141,7 @@ SDL_Surface * BitmapHandler::loadBitmapFromDir(std::string path, std::string fna
 		}
 		else
 		{
-			logGlobal->error("Failed to open %s via SDL_Image", fname);
+			logGlobal->error("Failed to open %s via SDL_Image", path.getOriginalName());
 			logGlobal->error("Reason: %s", IMG_GetError());
 			return nullptr;
 		}
@@ -187,19 +182,29 @@ SDL_Surface * BitmapHandler::loadBitmapFromDir(std::string path, std::string fna
 	{
 		CSDL_Ext::setDefaultColorKey(ret);
 	}
-
 	return ret;
 }
 
-SDL_Surface * BitmapHandler::loadBitmap(std::string fname)
+SDL_Surface * BitmapHandler::loadBitmap(const ImagePath & fname)
 {
-	SDL_Surface * bitmap = nullptr;
-
-	if (!(bitmap = loadBitmapFromDir("DATA/", fname)) &&
-		!(bitmap = loadBitmapFromDir("SPRITES/", fname)))
+	if(fname.empty())
 	{
-		logGlobal->error("Error: Failed to find file %s", fname);
+		logGlobal->warn("Call to loadBitmap with void fname!");
+		return nullptr;
 	}
 
-	return bitmap;
+	SDL_Surface * bitmap = loadBitmapFromDir(fname);
+	if (bitmap != nullptr)
+		return bitmap;
+
+	SDL_Surface * bitmapData = loadBitmapFromDir(fname.addPrefix("DATA/"));
+	if (bitmapData != nullptr)
+		return bitmapData;
+
+	SDL_Surface * bitmapSprites = loadBitmapFromDir(fname.addPrefix("SPRITES/"));
+	if (bitmapSprites != nullptr)
+		return bitmapSprites;
+
+	logGlobal->error("Error: Failed to find file %s", fname.getOriginalName());
+	return nullptr;
 }

+ 3 - 1
client/render/CBitmapHandler.h

@@ -9,10 +9,12 @@
  */
 #pragma once
 
+#include "../../lib/filesystem/ResourcePath.h"
+
 struct SDL_Surface;
 
 namespace BitmapHandler
 {
 	//Load file from /DATA or /SPRITES
-	SDL_Surface * loadBitmap(std::string fname);
+	SDL_Surface * loadBitmap(const ImagePath & fname);
 }

+ 5 - 5
client/render/CDefFile.cpp

@@ -24,7 +24,7 @@ class CFileCache
 	static const int cacheSize = 50; //Max number of cached files
 	struct FileData
 	{
-		ResourceID             name;
+		AnimationPath             name;
 		size_t                 size;
 		std::unique_ptr<ui8[]> data;
 
@@ -34,7 +34,7 @@ class CFileCache
 			std::copy(data.get(), data.get() + size, ret.get());
 			return ret;
 		}
-		FileData(ResourceID name_, size_t size_, std::unique_ptr<ui8[]> data_):
+		FileData(AnimationPath name_, size_t size_, std::unique_ptr<ui8[]> data_):
 			name{std::move(name_)},
 			size{size_},
 			data{std::move(data_)}
@@ -43,7 +43,7 @@ class CFileCache
 
 	std::deque<FileData> cache;
 public:
-	std::unique_ptr<ui8[]> getCachedFile(ResourceID rid)
+	std::unique_ptr<ui8[]> getCachedFile(AnimationPath rid)
 	{
 		for(auto & file : cache)
 		{
@@ -97,7 +97,7 @@ static bool colorsSimilar (const SDL_Color & lhs, const SDL_Color & rhs)
 	return std::abs(diffR) < threshold && std::abs(diffG) < threshold && std::abs(diffB) < threshold && std::abs(diffA) < threshold;
 }
 
-CDefFile::CDefFile(std::string Name):
+CDefFile::CDefFile(const AnimationPath & Name):
 	data(nullptr),
 	palette(nullptr)
 {
@@ -124,7 +124,7 @@ CDefFile::CDefFile(std::string Name):
 		{0, 0, 0, 64 }  // shadow border below selection ( used in battle def's )
 	};
 
-	data = animationCache.getCachedFile(ResourceID(std::string("SPRITES/") + Name, EResType::ANIMATION));
+	data = animationCache.getCachedFile(Name);
 
 	palette = std::unique_ptr<SDL_Color[]>(new SDL_Color[256]);
 	int it = 0;

+ 2 - 1
client/render/CDefFile.h

@@ -10,6 +10,7 @@
 #pragma once
 
 #include "../../lib/vcmi_endian.h"
+#include "../../lib/filesystem/ResourcePath.h"
 
 class IImageLoader;
 struct SDL_Color;
@@ -39,7 +40,7 @@ private:
 	std::unique_ptr<SDL_Color[]> palette;
 
 public:
-	CDefFile(std::string Name);
+	CDefFile(const AnimationPath & Name);
 	~CDefFile();
 
 	//load frame as SDL_Surface

+ 13 - 13
client/render/Graphics.cpp

@@ -24,6 +24,8 @@
 #include "../renderSDL/CTrueTypeFont.h"
 #include "../render/CAnimation.h"
 #include "../render/IImage.h"
+#include "../render/IRenderHandler.h"
+#include "../gui/CGuiHandler.h"
 
 #include "../lib/filesystem/Filesystem.h"
 #include "../lib/filesystem/CBinaryReader.h"
@@ -44,7 +46,7 @@ Graphics * graphics = nullptr;
 
 void Graphics::loadPaletteAndColors()
 {
-	auto textFile = CResourceHandler::get()->load(ResourceID("DATA/PLAYERS.PAL"))->readAll();
+	auto textFile = CResourceHandler::get()->load(ResourcePath("DATA/PLAYERS.PAL"))->readAll();
 	std::string pals((char*)textFile.first.get(), textFile.second);
 
 	int startPoint = 24; //beginning byte; used to read
@@ -62,7 +64,7 @@ void Graphics::loadPaletteAndColors()
 		}
 	}
 
-	auto stream = CResourceHandler::get()->load(ResourceID("config/NEUTRAL.PAL"));
+	auto stream = CResourceHandler::get()->load(ResourcePath("config/NEUTRAL.PAL"));
 	CBinaryReader reader(stream.get());
 
 	for(int i=0; i<32; ++i)
@@ -102,10 +104,10 @@ void Graphics::initializeBattleGraphics()
 	allConfigs.insert(allConfigs.begin(), ModScope::scopeBuiltin());
 	for(auto & mod : allConfigs)
 	{
-		if(!CResourceHandler::get(mod)->existsResource(ResourceID("config/battles_graphics.json")))
+		if(!CResourceHandler::get(mod)->existsResource(ResourcePath("config/battles_graphics.json")))
 			continue;
 			
-		const JsonNode config(mod, ResourceID("config/battles_graphics.json"));
+		const JsonNode config(mod, JsonPath::builtin("config/battles_graphics.json"));
 
 		//initialization of AC->def name mapping
 		if(!config["ac_mapping"].isNull())
@@ -204,7 +206,7 @@ void Graphics::blueToPlayersAdv(SDL_Surface * sur, PlayerColor player)
 
 void Graphics::loadFonts()
 {
-	const JsonNode config(ResourceID("config/fonts.json"));
+	const JsonNode config(JsonPath::builtin("config/fonts.json"));
 
 	const JsonVector & bmpConf = config["bitmap"].Vector();
 	const JsonNode   & ttfConf = config["trueType"];
@@ -228,7 +230,7 @@ void Graphics::loadFonts()
 void Graphics::loadErmuToPicture()
 {
 	//loading ERMU to picture
-	const JsonNode config(ResourceID("config/ERMU_to_picture.json"));
+	const JsonNode config(JsonPath::builtin("config/ERMU_to_picture.json"));
 	int etp_idx = 0;
 	for(const JsonNode &etp : config["ERMU_to_picture"].Vector()) {
 		int idx = 0;
@@ -279,16 +281,14 @@ void Graphics::initializeImageLists()
 	addImageListEntries(CGI->skills());
 }
 
-std::shared_ptr<CAnimation> Graphics::getAnimation(const std::string & path)
+std::shared_ptr<CAnimation> Graphics::getAnimation(const AnimationPath & path)
 {
-	ResourceID animationPath(path, EResType::ANIMATION);
+	if (cachedAnimations.count(path) != 0)
+		return cachedAnimations.at(path);
 
-	if (cachedAnimations.count(animationPath.getName()) != 0)
-		return cachedAnimations.at(animationPath.getName());
-
-	auto newAnimation = std::make_shared<CAnimation>(animationPath.getName());
+	auto newAnimation = GH.renderHandler().loadAnimation(path);
 
 	newAnimation->preload();
-	cachedAnimations[animationPath.getName()] = newAnimation;
+	cachedAnimations[path] = newAnimation;
 	return newAnimation;
 }

+ 3 - 2
client/render/Graphics.h

@@ -11,6 +11,7 @@
 
 #include "../lib/GameConstants.h"
 #include "../lib/Color.h"
+#include "../lib/filesystem/ResourcePath.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -42,10 +43,10 @@ class Graphics
 	void loadFonts();
 	void initializeImageLists();
 
-	std::map<std::string, std::shared_ptr<CAnimation>> cachedAnimations;
+	std::map<AnimationPath, std::shared_ptr<CAnimation>> cachedAnimations;
 
 public:
-	std::shared_ptr<CAnimation> getAnimation(const std::string & path);
+	std::shared_ptr<CAnimation> getAnimation(const AnimationPath & path);
 
 	//Fonts
 	static const int FONTS_NUMBER = 9;

+ 5 - 11
client/render/IImage.h

@@ -9,6 +9,8 @@
  */
 #pragma once
 
+#include "../../lib/filesystem/ResourcePath.h"
+
 VCMI_LIB_NAMESPACE_BEGIN
 
 class PlayerColor;
@@ -22,7 +24,7 @@ struct SDL_Surface;
 class ColorFilter;
 
 /// Defines which blit method will be selected when image is used for rendering
-enum class EImageBlitMode : uint8_t
+enum class EImageBlitMode
 {
 	/// Image can have no transparency and can be only used as background
 	OPAQUE,
@@ -80,15 +82,7 @@ public:
 	virtual void verticalFlip() = 0;
 	virtual void doubleFlip() = 0;
 
-	IImage();
-	virtual ~IImage();
-
-	/// loads image from specified file. Returns 0-sized images on failure
-	static std::shared_ptr<IImage> createFromFile( const std::string & path );
-	static std::shared_ptr<IImage> createFromFile( const std::string & path, EImageBlitMode mode );
-
-	/// temporary compatibility method. Creates IImage from existing SDL_Surface
-	/// Surface will be shared, called must still free it with SDL_FreeSurface
-	static std::shared_ptr<IImage> createFromSurface( SDL_Surface * source );
+	IImage() = default;
+	virtual ~IImage() = default;
 };
 

+ 38 - 0
client/render/IRenderHandler.h

@@ -0,0 +1,38 @@
+/*
+ * IRenderHandler.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "../../lib/filesystem/ResourcePath.h"
+
+struct SDL_Surface;
+
+class IImage;
+class CAnimation;
+enum class EImageBlitMode;
+
+class IRenderHandler : public boost::noncopyable
+{
+public:
+	virtual ~IRenderHandler() = default;
+
+	/// Loads image using given path
+	virtual std::shared_ptr<IImage> loadImage(const ImagePath & path) = 0;
+	virtual std::shared_ptr<IImage> loadImage(const ImagePath & path, EImageBlitMode mode) = 0;
+
+	/// temporary compatibility method. Creates IImage from existing SDL_Surface
+	/// Surface will be shared, caller must still free it with SDL_FreeSurface
+	virtual std::shared_ptr<IImage> createImage(SDL_Surface * source) = 0;
+
+	/// Loads animation using given path
+	virtual std::shared_ptr<CAnimation> loadAnimation(const AnimationPath & path) = 0;
+
+	/// Creates empty CAnimation
+	virtual std::shared_ptr<CAnimation> createAnimation() = 0;
+};

+ 2 - 2
client/renderSDL/CBitmapFont.cpp

@@ -24,7 +24,7 @@
 
 #include <SDL_surface.h>
 
-void CBitmapFont::loadModFont(const std::string & modName, const ResourceID & resource)
+void CBitmapFont::loadModFont(const std::string & modName, const ResourcePath & resource)
 {
 	if (!CResourceHandler::get(modName)->existsResource(resource))
 	{
@@ -72,7 +72,7 @@ void CBitmapFont::loadModFont(const std::string & modName, const ResourceID & re
 CBitmapFont::CBitmapFont(const std::string & filename):
 	maxHeight(0)
 {
-	ResourceID resource("data/" + filename, EResType::BMP_FONT);
+	ResourcePath resource("data/" + filename, EResType::BMP_FONT);
 
 	loadModFont("core", resource);
 

+ 2 - 2
client/renderSDL/CBitmapFont.h

@@ -12,7 +12,7 @@
 #include "../render/IFont.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
-class ResourceID;
+class ResourcePath;
 VCMI_LIB_NAMESPACE_END
 
 class CBitmapFont : public IFont
@@ -31,7 +31,7 @@ class CBitmapFont : public IFont
 	std::unordered_map<CodePoint, BitmapChar> chars;
 	uint32_t maxHeight;
 
-	void loadModFont(const std::string & modName, const ResourceID & resource);
+	void loadModFont(const std::string & modName, const ResourcePath & resource);
 
 	void renderCharacter(SDL_Surface * surface, const BitmapChar & character, const ColorRGBA & color, int &posX, int &posY) const;
 	void renderText(SDL_Surface * surface, const std::string & data, const ColorRGBA & color, const Point & pos) const override;

+ 1 - 1
client/renderSDL/CBitmapHanFont.cpp

@@ -98,7 +98,7 @@ void CBitmapHanFont::renderText(SDL_Surface * surface, const std::string & data,
 
 CBitmapHanFont::CBitmapHanFont(const JsonNode &config):
 	fallback(new CBitmapFont(config["fallback"].String())),
-	data(CResourceHandler::get()->load(ResourceID("data/" + config["name"].String(), EResType::OTHER))->readAll()),
+	data(CResourceHandler::get()->load(ResourcePath("data/" + config["name"].String(), EResType::OTHER))->readAll()),
 	size((size_t)config["size"].Float())
 {
 	// basic tests to make sure that fonts are OK

+ 1 - 1
client/renderSDL/CTrueTypeFont.cpp

@@ -24,7 +24,7 @@
 std::pair<std::unique_ptr<ui8[]>, ui64> CTrueTypeFont::loadData(const JsonNode & config)
 {
 	std::string filename = "Data/" + config["file"].String();
-	return CResourceHandler::get()->load(ResourceID(filename, EResType::TTF_FONT))->readAll();
+	return CResourceHandler::get()->load(ResourcePath(filename, EResType::TTF_FONT))->readAll();
 }
 
 TTF_Font * CTrueTypeFont::loadFont(const JsonNode &config)

+ 40 - 0
client/renderSDL/RenderHandler.cpp

@@ -0,0 +1,40 @@
+/*
+ * RenderHandler.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#include "StdInc.h"
+#include "RenderHandler.h"
+
+#include "../render/CAnimation.h"
+#include "SDLImage.h"
+
+
+std::shared_ptr<IImage> RenderHandler::loadImage(const ImagePath & path)
+{
+	return loadImage(path, EImageBlitMode::ALPHA);
+}
+
+std::shared_ptr<IImage> RenderHandler::loadImage(const ImagePath & path, EImageBlitMode mode)
+{
+	return std::make_shared<SDLImage>(path, mode);
+}
+
+std::shared_ptr<IImage> RenderHandler::createImage(SDL_Surface * source)
+{
+	return std::make_shared<SDLImage>(source, EImageBlitMode::ALPHA);
+}
+
+std::shared_ptr<CAnimation> RenderHandler::loadAnimation(const AnimationPath & path)
+{
+	return std::make_shared<CAnimation>(path);
+}
+
+std::shared_ptr<CAnimation> RenderHandler::createAnimation()
+{
+	return std::make_shared<CAnimation>();
+}

+ 25 - 0
client/renderSDL/RenderHandler.h

@@ -0,0 +1,25 @@
+/*
+ * RenderHandler.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+#pragma once
+
+#include "../render/IRenderHandler.h"
+
+class RenderHandler : public IRenderHandler
+{
+public:
+	std::shared_ptr<IImage> loadImage(const ImagePath & path) override;
+	std::shared_ptr<IImage> loadImage(const ImagePath & path, EImageBlitMode mode) override;
+
+	std::shared_ptr<IImage> createImage(SDL_Surface * source) override;
+
+	std::shared_ptr<CAnimation> loadAnimation(const AnimationPath & path) override;
+
+	std::shared_ptr<CAnimation> createAnimation() override;
+};

+ 3 - 23
client/renderSDL/SDLImage.cpp

@@ -24,24 +24,6 @@
 
 class SDLImageLoader;
 
-std::shared_ptr<IImage> IImage::createFromFile( const std::string & path )
-{
-	return createFromFile(path, EImageBlitMode::ALPHA);
-}
-
-std::shared_ptr<IImage> IImage::createFromFile( const std::string & path, EImageBlitMode mode )
-{
-	return std::shared_ptr<IImage>(new SDLImage(path, mode));
-}
-
-std::shared_ptr<IImage> IImage::createFromSurface( SDL_Surface * source )
-{
-	return std::shared_ptr<IImage>(new SDLImage(source, EImageBlitMode::ALPHA));
-}
-
-IImage::IImage() = default;
-IImage::~IImage() = default;
-
 int IImage::width() const
 {
 	return dimensions().x;
@@ -89,9 +71,7 @@ SDLImage::SDLImage(const JsonNode & conf, EImageBlitMode mode)
 	fullSize(0, 0),
 	originalPalette(nullptr)
 {
-	std::string filename = conf["file"].String();
-
-	surf = BitmapHandler::loadBitmap(filename);
+	surf = BitmapHandler::loadBitmap(ImagePath::fromJson(conf["file"]));
 
 	if(surf == nullptr)
 		return;
@@ -118,7 +98,7 @@ SDLImage::SDLImage(const JsonNode & conf, EImageBlitMode mode)
 	}
 }
 
-SDLImage::SDLImage(std::string filename, EImageBlitMode mode)
+SDLImage::SDLImage(const ImagePath & filename, EImageBlitMode mode)
 	: surf(nullptr),
 	margins(0, 0),
 	fullSize(0, 0),
@@ -128,7 +108,7 @@ SDLImage::SDLImage(std::string filename, EImageBlitMode mode)
 
 	if(surf == nullptr)
 	{
-		logGlobal->error("Error: failed to load image %s", filename);
+		logGlobal->error("Error: failed to load image %s", filename.getOriginalName());
 		return;
 	}
 	else

+ 1 - 1
client/renderSDL/SDLImage.h

@@ -43,7 +43,7 @@ public:
 	//Load image from def file
 	SDLImage(CDefFile *data, size_t frame, size_t group=0);
 	//Load from bitmap file
-	SDLImage(std::string filename, EImageBlitMode blitMode);
+	SDLImage(const ImagePath & filename, EImageBlitMode blitMode);
 
 	SDLImage(const JsonNode & conf, EImageBlitMode blitMode);
 	//Create using existing surface, extraRef will increase refcount on SDL_Surface

+ 5 - 4
client/widgets/Buttons.cpp

@@ -25,6 +25,7 @@
 #include "../windows/InfoWindows.h"
 #include "../render/CAnimation.h"
 #include "../render/Canvas.h"
+#include "../render/IRenderHandler.h"
 
 #include "../../lib/CConfigHandler.h"
 #include "../../lib/CGeneralTextHandler.h"
@@ -89,7 +90,7 @@ void CButton::addOverlay(std::shared_ptr<CIntObject> newOverlay)
 	update();
 }
 
-void CButton::addImage(std::string filename)
+void CButton::addImage(const AnimationPath & filename)
 {
 	imageNames.push_back(filename);
 }
@@ -232,7 +233,7 @@ void CButton::hover (bool on)
 	}
 }
 
-CButton::CButton(Point position, const std::string &defName, const std::pair<std::string, std::string> &help, CFunctionList<void()> Callback, EShortcut key, bool playerColoredButton):
+CButton::CButton(Point position, const AnimationPath &defName, const std::pair<std::string, std::string> &help, CFunctionList<void()> Callback, EShortcut key, bool playerColoredButton):
     CKeyShortcut(key),
     callback(Callback)
 {
@@ -268,7 +269,7 @@ void CButton::setIndex(size_t index)
 	if (index == currentImage || index>=imageNames.size())
 		return;
 	currentImage = index;
-	auto anim = std::make_shared<CAnimation>(imageNames[index]);
+	auto anim = GH.renderHandler().loadAnimation(imageNames[index]);
 	setImage(anim);
 }
 
@@ -357,7 +358,7 @@ void CToggleBase::addCallback(std::function<void(bool)> function)
 	callback += function;
 }
 
-CToggleButton::CToggleButton(Point position, const std::string &defName, const std::pair<std::string, std::string> &help,
+CToggleButton::CToggleButton(Point position, const AnimationPath &defName, const std::pair<std::string, std::string> &help,
 							 CFunctionList<void(bool)> callback, EShortcut key, bool playerColoredButton):
   CButton(position, defName, help, 0, key, playerColoredButton),
   CToggleBase(callback)

+ 5 - 4
client/widgets/Buttons.h

@@ -12,6 +12,7 @@
 #include "../gui/CIntObject.h"
 #include "../render/EFont.h"
 #include "../../lib/FunctionList.h"
+#include "../../lib/filesystem/ResourcePath.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 class Rect;
@@ -36,7 +37,7 @@ public:
 		HIGHLIGHTED=3
 	};
 protected:
-	std::vector<std::string> imageNames;//store list of images that can be used by this button
+	std::vector<AnimationPath> imageNames;//store list of images that can be used by this button
 	size_t currentImage;
 
 	ButtonState state;//current state of button from enum
@@ -72,7 +73,7 @@ public:
 	void addOverlay(std::shared_ptr<CIntObject> newOverlay);
 	void addTextOverlay(const std::string & Text, EFonts font, ColorRGBA color);
 
-	void addImage(std::string filename);
+	void addImage(const AnimationPath & filename);
 	void addHoverText(ButtonState state, std::string text);
 
 	void setImageOrder(int state1, int state2, int state3, int state4);
@@ -84,7 +85,7 @@ public:
 	bool isHighlighted();
 
 	/// Constructor
-	CButton(Point position, const std::string & defName, const std::pair<std::string, std::string> & help,
+	CButton(Point position, const AnimationPath & defName, const std::pair<std::string, std::string> & help,
 			CFunctionList<void()> Callback = 0, EShortcut key = {}, bool playerColoredButton = false );
 
 	/// Appearance modifiers
@@ -145,7 +146,7 @@ class CToggleButton : public CButton, public CToggleBase
 	void setEnabled(bool enabled) override;
 
 public:
-	CToggleButton(Point position, const std::string &defName, const std::pair<std::string, std::string> &help,
+	CToggleButton(Point position, const AnimationPath &defName, const std::pair<std::string, std::string> &help,
 				  CFunctionList<void(bool)> Callback = 0, EShortcut key = {}, bool playerColoredButton = false );
 
 	void clickPressed(const Point & cursorPosition) override;

+ 3 - 3
client/widgets/CArtifactHolder.cpp

@@ -89,7 +89,7 @@ void CCommanderArtPlace::createImage()
 	if(ourArt)
 		imageIndex = ourArt->artType->getIconIndex();
 
-	image = std::make_shared<CAnimImage>("artifact", imageIndex);
+	image = std::make_shared<CAnimImage>(AnimationPath::builtin("artifact"), imageIndex);
 	if(!ourArt)
 		image->disable();
 }
@@ -247,11 +247,11 @@ void CHeroArtPlace::createImage()
 	else if(ourArt)
 		imageIndex = ourArt->artType->getIconIndex();
 
-	image = std::make_shared<CAnimImage>("artifact", imageIndex);
+	image = std::make_shared<CAnimImage>(AnimationPath::builtin("artifact"), imageIndex);
 	if(!ourArt)
 		image->disable();
 
-	selection = std::make_shared<CAnimImage>("artifact", ArtifactID::ART_SELECTION);
+	selection = std::make_shared<CAnimImage>(AnimationPath::builtin("artifact"), ArtifactID::ART_SELECTION);
 	selection->disable();
 }
 

+ 1 - 1
client/widgets/CArtifactsOfHeroBackpack.cpp

@@ -42,7 +42,7 @@ CArtifactsOfHeroBackpack::CArtifactsOfHeroBackpack(const Point & position)
 
 	for(int i = 0; i < visibleCapacityMax; i++)
 	{
-		auto artifactSlotBackground = std::make_shared<CPicture>("heroWindow/artifactSlotEmpty",
+		auto artifactSlotBackground = std::make_shared<CPicture>( ImagePath::builtin("heroWindow/artifactSlotEmpty"),
 			Point(slotSizeWithMargin * (i % HERO_BACKPACK_WINDOW_SLOT_COLUMNS), slotSizeWithMargin * (i / HERO_BACKPACK_WINDOW_SLOT_COLUMNS)));
 
 		backpackSlotsBackgrounds.emplace_back(artifactSlotBackground);

+ 2 - 2
client/widgets/CArtifactsOfHeroBase.cpp

@@ -86,8 +86,8 @@ void CArtifactsOfHeroBase::init(
 		artPlace->leftClickCallback = lClickCallback;
 		artPlace->rightClickCallback = rClickCallback;
 	}
-	leftBackpackRoll = std::make_shared<CButton>(Point(379, 364), "hsbtns3.def", CButton::tooltip(), [scrollHandler]() { scrollHandler(-1); }, EShortcut::MOVE_LEFT);
-	rightBackpackRoll = std::make_shared<CButton>(Point(632, 364), "hsbtns5.def", CButton::tooltip(), [scrollHandler]() { scrollHandler(+1); }, EShortcut::MOVE_RIGHT);
+	leftBackpackRoll = std::make_shared<CButton>(Point(379, 364), AnimationPath::builtin("hsbtns3.def"), CButton::tooltip(), [scrollHandler]() { scrollHandler(-1); }, EShortcut::MOVE_LEFT);
+	rightBackpackRoll = std::make_shared<CButton>(Point(632, 364), AnimationPath::builtin("hsbtns5.def"), CButton::tooltip(), [scrollHandler]() { scrollHandler(+1); }, EShortcut::MOVE_RIGHT);
 	leftBackpackRoll->block(true);
 	rightBackpackRoll->block(true);
 }

+ 6 - 6
client/widgets/CComponent.cpp

@@ -102,7 +102,7 @@ void CComponent::init(Etype Type, int Subtype, int Val, ESize imageSize, EFonts
 	}
 }
 
-const std::vector<std::string> CComponent::getFileName()
+std::vector<AnimationPath> CComponent::getFileName()
 {
 	static const std::string  primSkillsArr [] = {"PSKIL32",        "PSKIL32",        "PSKIL42",        "PSKILL"};
 	static const std::string  secSkillsArr [] =  {"SECSK32",        "SECSK32",        "SECSKILL",       "SECSK82"};
@@ -115,9 +115,9 @@ const std::vector<std::string> CComponent::getFileName()
 	static const std::string  heroArr [] =       {"PortraitsSmall", "PortraitsSmall", "PortraitsSmall", "PortraitsLarge"};
 	static const std::string  flagArr [] =       {"CREST58",        "CREST58",        "CREST58",        "CREST58"};
 
-	auto gen = [](const std::string * arr)
+	auto gen = [](const std::string * arr) -> std::vector<AnimationPath>
 	{
-		return std::vector<std::string>(arr, arr + 4);
+		return { AnimationPath::builtin(arr[0]), AnimationPath::builtin(arr[1]), AnimationPath::builtin(arr[2]), AnimationPath::builtin(arr[3]) };
 	};
 
 	switch(compType)
@@ -131,12 +131,12 @@ const std::vector<std::string> CComponent::getFileName()
 	case spell:      return gen(spellsArr);
 	case morale:     return gen(moraleArr);
 	case luck:       return gen(luckArr);
-	case building:   return std::vector<std::string>(4, (*CGI->townh)[subtype]->town->clientInfo.buildingsIcons);
+	case building:   return std::vector<AnimationPath>(4, (*CGI->townh)[subtype]->town->clientInfo.buildingsIcons);
 	case hero:       return gen(heroArr);
 	case flag:       return gen(flagArr);
 	}
 	assert(0);
-	return std::vector<std::string>();
+	return {};
 }
 
 size_t CComponent::getIndex()
@@ -251,7 +251,7 @@ std::string CComponent::getSubtitleInternal()
 	return "";
 }
 
-void CComponent::setSurface(std::string defName, int imgPos)
+void CComponent::setSurface(const AnimationPath & defName, int imgPos)
 {
 	OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
 	image = std::make_shared<CAnimImage>(defName, imgPos);

+ 3 - 3
client/widgets/CComponent.h

@@ -11,7 +11,7 @@
 
 #include "../gui/CIntObject.h"
 #include "../render/EFont.h"
-
+#include "../../lib/filesystem/ResourcePath.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -45,8 +45,8 @@ private:
 	std::vector<std::shared_ptr<CLabel>> lines;
 
 	size_t getIndex();
-	const std::vector<std::string> getFileName();
-	void setSurface(std::string defName, int imgPos);
+	std::vector<AnimationPath> getFileName();
+	void setSurface(const AnimationPath & defName, int imgPos);
 	std::string getSubtitleInternal();
 
 	void init(Etype Type, int Subtype, int Val, ESize imageSize, EFonts font = FONT_SMALL);

+ 1 - 1
client/widgets/CGarrisonInt.cpp

@@ -421,7 +421,7 @@ CGarrisonSlot::CGarrisonSlot(CGarrisonInt * Owner, int x, int y, SlotID IID, EGa
 	pos.x += x;
 	pos.y += y;
 
-	std::string imgName = owner->smallIcons ? "cprsmall" : "TWCRPORT";
+	AnimationPath imgName = AnimationPath::builtin(owner->smallIcons ? "cprsmall" : "TWCRPORT");
 
 	creatureImage = std::make_shared<CAnimImage>(graphics->getAnimation(imgName), 0);
 	creatureImage->disable();

+ 1 - 1
client/widgets/CWindowWithArtifacts.cpp

@@ -271,7 +271,7 @@ void CWindowWithArtifacts::artifactMoved(const ArtifactLocation & srcLoc, const
 			{
 				if(artSetPtr->isActive())
 				{
-					CCS->curh->dragAndDropCursor("artifact", pickedArtInst->artType->getIconIndex());
+					CCS->curh->dragAndDropCursor(AnimationPath::builtin("artifact"), pickedArtInst->artType->getIconIndex());
 					if(srcLoc.isHolder(hero) || !std::is_same_v<decltype(artSetWeak), std::weak_ptr<CArtifactsOfHeroKingdom>>)
 						artSetPtr->markPossibleSlots(pickedArtInst, hero->tempOwner == LOCPLINT->playerID);
 				}

+ 1 - 1
client/widgets/ComboBox.cpp

@@ -155,7 +155,7 @@ void ComboBox::DropDown::setItem(const void * item)
 	GH.windows().popWindows(1);
 }
 
-ComboBox::ComboBox(Point position, const std::string & defName, const std::pair<std::string, std::string> & help, const JsonNode & dropDownDescriptor, EShortcut key, bool playerColoredButton):
+ComboBox::ComboBox(Point position, const AnimationPath & defName, const std::pair<std::string, std::string> & help, const JsonNode & dropDownDescriptor, EShortcut key, bool playerColoredButton):
 	CButton(position, defName, help, 0, key, playerColoredButton)
 {
 	addCallback([&, dropDownDescriptor]()

+ 1 - 1
client/widgets/ComboBox.h

@@ -54,7 +54,7 @@ class ComboBox : public CButton
 	void setItem(const void *);
 
 public:
-	ComboBox(Point position, const std::string & defName, const std::pair<std::string, std::string> & help, const JsonNode & dropDownDescriptor, EShortcut key = {}, bool playerColoredButton = false);
+	ComboBox(Point position, const AnimationPath & defName, const std::pair<std::string, std::string> & help, const JsonNode & dropDownDescriptor, EShortcut key = {}, bool playerColoredButton = false);
 	
 	//define this callback to fill input vector with data for the combo box
 	std::function<void(std::vector<const void *> &)> onConstructItems;

Some files were not shown because too many files changed in this diff