2
0
Эх сурвалжийг харах

Merge branch 'develop-upstream' into battle-dialog

nordsoft 3 жил өмнө
parent
commit
44e469a2c7

+ 10 - 4
AI/Nullkiller/AIUtility.cpp

@@ -237,7 +237,7 @@ bool isObjectPassable(const Nullkiller * ai, const CGObjectInstance * obj)
 
 bool isObjectPassable(const CGObjectInstance * obj)
 {
-	return isObjectPassable(obj, ai->playerID, cb->getPlayerRelations(obj->tempOwner, ai->playerID));
+	return isObjectPassable(obj, ai->playerID, ai->myCb->getPlayerRelations(obj->tempOwner, ai->playerID));
 }
 
 // Pathfinder internal helper
@@ -344,11 +344,14 @@ uint64_t timeElapsed(std::chrono::time_point<std::chrono::high_resolution_clock>
 // todo: move to obj manager
 bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObjectInstance * obj)
 {
+	auto relations = ai->cb->getPlayerRelations(obj->tempOwner, h->tempOwner);
+
 	switch(obj->ID)
 	{
 	case Obj::TOWN:
 	case Obj::HERO: //never visit our heroes at random
-		return obj->tempOwner != h->tempOwner; //do not visit our towns at random
+		return relations == PlayerRelations::ENEMIES; //do not visit our towns at random
+
 	case Obj::BORDER_GATE:
 	{
 		for(auto q : ai->cb->getMyQuests())
@@ -378,9 +381,12 @@ bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObject
 	}
 	case Obj::CREATURE_GENERATOR1:
 	{
-		if(obj->tempOwner != h->tempOwner)
+		if(relations == PlayerRelations::ENEMIES)
 			return true; //flag just in case
 
+		if(relations == PlayerRelations::ALLIES)
+			return false;
+
 		const CGDwelling * d = dynamic_cast<const CGDwelling *>(obj);
 
 		for(auto level : d->creatures)
@@ -420,7 +426,7 @@ bool shouldVisit(const Nullkiller * ai, const CGHeroInstance * h, const CGObject
 		break;
 	}
 	case Obj::LIBRARY_OF_ENLIGHTENMENT:
-		if(h->level < 12)
+		if(h->level < 10)
 			return false;
 		break;
 	case Obj::TREE_OF_KNOWLEDGE:

+ 2 - 0
client/CMT.cpp

@@ -1387,6 +1387,8 @@ static void handleEvent(SDL_Event & ev)
 				{
 					if(ourCampaign->mapsRemaining.size())
 					{
+						GH.pushInt(CMM);
+						GH.pushInt(CMM->menu);
 						CMM->openCampaignLobby(ourCampaign);
 					}
 				};

+ 49 - 52
client/CMusicHandler.cpp

@@ -34,17 +34,6 @@ static std::string sounds[] = {
 #undef VCMI_SOUND_NAME
 #undef VCMI_SOUND_FILE
 
-// Not pretty, but there's only one music handler object in the game.
-static void soundFinishedCallbackC(int channel)
-{
-	CCS->soundh->soundFinishedCallback(channel);
-}
-
-static void musicFinishedCallbackC()
-{
-	CCS->musich->musicFinishedCallback();
-}
-
 void CAudioBase::init()
 {
 	if (initialized)
@@ -140,8 +129,10 @@ void CSoundHandler::init()
 
 	if (initialized)
 	{
-		// Load sounds
-		Mix_ChannelFinished(soundFinishedCallbackC);
+		Mix_ChannelFinished([](int channel)
+		{
+			CCS->soundh->soundFinishedCallback(channel);
+		});
 	}
 }
 
@@ -244,7 +235,7 @@ int CSoundHandler::playSoundFromSet(std::vector<soundBase::soundID> &sound_vec)
 	return playSound(*RandomGeneratorUtil::nextItem(sound_vec, CRandomGenerator::getDefault()));
 }
 
-void CSoundHandler::stopSound( int handler )
+void CSoundHandler::stopSound(int handler)
 {
 	if (initialized && handler != -1)
 		Mix_HaltChannel(handler);
@@ -368,24 +359,24 @@ CMusicHandler::CMusicHandler():
 	for(const ResourceID & file : mp3files)
 	{
 		if(boost::algorithm::istarts_with(file.getName(), "MUSIC/Combat"))
-			addEntryToSet("battle", file.getName(), file.getName());
+			addEntryToSet("battle", file.getName());
 		else if(boost::algorithm::istarts_with(file.getName(), "MUSIC/AITheme"))
-			addEntryToSet("enemy-turn", file.getName(), file.getName());
+			addEntryToSet("enemy-turn", file.getName());
 	}
 
 }
 
-void CMusicHandler::loadTerrainSounds()
+void CMusicHandler::loadTerrainMusicThemes()
 {
 	for (const auto & terrain : CGI->terrainTypeHandler->terrains())
 	{
-		addEntryToSet("terrain", terrain.name, "Music/" + terrain.musicFilename);
+		addEntryToSet("terrain_" + terrain.name, "Music/" + terrain.musicFilename);
 	}
 }
 
-void CMusicHandler::addEntryToSet(const std::string & set, const std::string & musicID, const std::string & musicURI)
+void CMusicHandler::addEntryToSet(const std::string & set, const std::string & musicURI)
 {
-	musicsSet[set][musicID] = musicURI;
+	musicsSet[set].push_back(musicURI);
 }
 
 void CMusicHandler::init()
@@ -393,7 +384,12 @@ void CMusicHandler::init()
 	CAudioBase::init();
 
 	if (initialized)
-		Mix_HookMusicFinished(musicFinishedCallbackC);
+	{
+		Mix_HookMusicFinished([]()
+		{
+			CCS->musich->musicFinishedCallback();
+		});
+	}
 }
 
 void CMusicHandler::release()
@@ -413,29 +409,18 @@ void CMusicHandler::release()
 
 void CMusicHandler::playMusic(const std::string & musicURI, bool loop, bool fromStart)
 {
-	if (current && current->isTrack(musicURI))
+	if (current && current->isPlaying() && current->isTrack(musicURI))
 		return;
 
 	queueNext(this, "", musicURI, loop, fromStart);
 }
 
-void CMusicHandler::playMusicFromSet(const std::string & whichSet, bool loop, bool fromStart)
+void CMusicHandler::playMusicFromSet(const std::string & musicSet, const std::string & entryID, bool loop, bool fromStart)
 {
-	auto selectedSet = musicsSet.find(whichSet);
-	if (selectedSet == musicsSet.end())
-	{
-		logGlobal->error("Error: playing music from non-existing set: %s", whichSet);
-		return;
-	}
-
-	if (current && current->isSet(whichSet))
-		return;
-
-	// in this mode - play random track from set
-	queueNext(this, whichSet, "", loop, fromStart);
+	playMusicFromSet(musicSet + "_" + entryID, loop, fromStart);
 }
 
-void CMusicHandler::playMusicFromSet(const std::string & whichSet, const std::string & entryID, bool loop,  bool fromStart)
+void CMusicHandler::playMusicFromSet(const std::string & whichSet, bool loop, bool fromStart)
 {
 	auto selectedSet = musicsSet.find(whichSet);
 	if (selectedSet == musicsSet.end())
@@ -444,18 +429,11 @@ void CMusicHandler::playMusicFromSet(const std::string & whichSet, const std::st
 		return;
 	}
 
-	auto selectedEntry = selectedSet->second.find(entryID);
-	if (selectedEntry == selectedSet->second.end())
-	{
-		logGlobal->error("Error: playing non-existing entry %s from set: %s", entryID, whichSet);
-		return;
-	}
-
-	if (current && current->isTrack(selectedEntry->second))
+	if (current && current->isPlaying() && current->isSet(whichSet))
 		return;
 
-	// in this mode - play specific track from set
-	queueNext(this, "", selectedEntry->second, loop, fromStart);
+	// in this mode - play random track from set
+	queueNext(this, whichSet, "", loop, fromStart);
 }
 
 void CMusicHandler::queueNext(std::unique_ptr<MusicEntry> queued)
@@ -513,7 +491,7 @@ void CMusicHandler::musicFinishedCallback()
 
 	if (current.get() != nullptr)
 	{
-		//return if current music still not finished
+		// if music is looped, play it again
 		if (current->play())
 			return;
 		else
@@ -530,6 +508,7 @@ void CMusicHandler::musicFinishedCallback()
 MusicEntry::MusicEntry(CMusicHandler *owner, std::string setName, std::string musicURI, bool looped, bool fromStart):
 	owner(owner),
 	music(nullptr),
+	playing(false),
 	startTime(uint32_t(-1)),
 	startPosition(0),
 	loop(looped ? -1 : 1),
@@ -578,29 +557,41 @@ bool MusicEntry::play()
 	if (!setName.empty())
 	{
 		const auto & set = owner->musicsSet[setName];
-		load(RandomGeneratorUtil::nextItem(set, CRandomGenerator::getDefault())->second);
+		const auto & iter = RandomGeneratorUtil::nextItem(set, CRandomGenerator::getDefault());
+		load(*iter);
 	}
 
 	logGlobal->trace("Playing music file %s", currentName);
 
-	if ( !fromStart && owner->trackPositions.count(currentName) > 0 && owner->trackPositions[currentName] > 0)
+	if (!fromStart && owner->trackPositions.count(currentName) > 0 && owner->trackPositions[currentName] > 0)
 	{
 		float timeToStart = owner->trackPositions[currentName];
 		startPosition = std::round(timeToStart * 1000);
 
+		// erase stored position:
+		// if music track will be interrupted again - new position will be written in stop() method
+		// if music track is not interrupted and will finish by timeout/end of file - it will restart from begginning as it should
+		owner->trackPositions.erase(owner->trackPositions.find(currentName));
+
 		if (Mix_FadeInMusicPos(music, 1, 1000, timeToStart) == -1)
 		{
 			logGlobal->error("Unable to play music (%s)", Mix_GetError());
 			return false;
 		}
 	}
-	else if(Mix_PlayMusic(music, 1) == -1)
+	else
 	{
-		logGlobal->error("Unable to play music (%s)", Mix_GetError());
-		return false;
+		startPosition = 0;
+
+		if(Mix_PlayMusic(music, 1) == -1)
+		{
+			logGlobal->error("Unable to play music (%s)", Mix_GetError());
+			return false;
+		}
 	}
 
 	startTime = SDL_GetTicks();
+	playing = true;
 	return true;
 }
 
@@ -608,6 +599,7 @@ bool MusicEntry::stop(int fade_ms)
 {
 	if (Mix_PlayingMusic())
 	{
+		playing = false;
 		loop = 0;
 		uint32_t endTime = SDL_GetTicks();
 		assert(startTime != uint32_t(-1));
@@ -621,6 +613,11 @@ bool MusicEntry::stop(int fade_ms)
 	return false;
 }
 
+bool MusicEntry::isPlaying()
+{
+	return playing;
+}
+
 bool MusicEntry::isSet(std::string set)
 {
 	return !setName.empty() && set == setName;

+ 14 - 11
client/CMusicHandler.h

@@ -100,22 +100,23 @@ class MusicEntry
 
 	int loop; // -1 = indefinite
 	bool fromStart;
+	bool playing;
 	uint32_t startTime;
 	uint32_t startPosition;
 	//if not null - set from which music will be randomly selected
 	std::string setName;
 	std::string currentName;
 
-
 	void load(std::string musicURI);
 
 public:
-	bool isSet(std::string setName);
-	bool isTrack(std::string trackName);
-
 	MusicEntry(CMusicHandler *owner, std::string setName, std::string musicURI, bool looped, bool fromStart);
 	~MusicEntry();
 
+	bool isSet(std::string setName);
+	bool isTrack(std::string trackName);
+	bool isPlaying();
+
 	bool play();
 	bool stop(int fade_ms=0);
 };
@@ -123,7 +124,6 @@ public:
 class CMusicHandler: public CAudioBase
 {
 private:
-	
 	//update volume on configuration change
 	SettingsListener listener;
 	void onVolumeChange(const JsonNode &volumeNode);
@@ -133,18 +133,21 @@ private:
 
 	void queueNext(CMusicHandler *owner, const std::string & setName, const std::string & musicURI, bool looped, bool fromStart);
 	void queueNext(std::unique_ptr<MusicEntry> queued);
+	void musicFinishedCallback();
 
-	std::map<std::string, std::map<std::string, std::string>> musicsSet;
+	/// map <set name> -> <list of URI's to tracks belonging to the said set>
+	std::map<std::string, std::vector<std::string>> musicsSet;
+	/// stored position, in seconds at which music player should resume playing this track
 	std::map<std::string, 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 & entryID, const std::string & musicURI);
+	void addEntryToSet(const std::string & set, const std::string & musicURI);
 
 	void init() override;
-	void loadTerrainSounds();
+	void loadTerrainMusicThemes();
 	void release() override;
 	void setVolume(ui32 percent) override;
 
@@ -152,10 +155,10 @@ public:
 	void playMusic(const std::string & musicURI, bool loop, bool fromStart);
 	/// play random track from this set
 	void playMusicFromSet(const std::string & musicSet, bool loop, bool fromStart);
-	/// play specific track from set
+	/// play random track from set (musicSet, entryID)
 	void playMusicFromSet(const std::string & musicSet, const std::string & entryID, bool loop, bool fromStart);
+	/// stops currently playing music by fading out it over fade_ms and starts next scheduled track, if any
 	void stopMusic(int fade_ms=1000);
-	void musicFinishedCallback();
 
 	friend class MusicEntry;
 };

+ 104 - 413
client/CPlayerInterface.cpp

@@ -152,7 +152,7 @@ void CPlayerInterface::init(std::shared_ptr<Environment> ENV, std::shared_ptr<CC
 	env = ENV;
 
 	CCS->soundh->loadHorseSounds();
-	CCS->musich->loadTerrainSounds();
+	CCS->musich->loadTerrainMusicThemes();
 
 	initializeHeroTownList();
 
@@ -218,27 +218,6 @@ void CPlayerInterface::yourTurn()
 	acceptTurn();
 }
 
-STRONG_INLINE void subRect(const int & x, const int & y, const int & z, const SDL_Rect & r, const ObjectInstanceID & hid)
-{
-	TerrainTile2 & hlp = CGI->mh->ttiles[z][x][y];
-	for (auto & elem : hlp.objects)
-		if (elem.obj && elem.obj->id == hid)
-		{
-			elem.rect = r;
-			return;
-		}
-}
-
-STRONG_INLINE void delObjRect(const int & x, const int & y, const int & z, const ObjectInstanceID & hid)
-{
-	TerrainTile2 & hlp = CGI->mh->ttiles[z][x][y];
-	for (int h=0; h<hlp.objects.size(); ++h)
-		if (hlp.objects[h].obj && hlp.objects[h].obj->id == hid)
-		{
-			hlp.objects.erase(hlp.objects.begin()+h);
-			return;
-		}
-}
 void CPlayerInterface::heroMoved(const TryMoveHero & details, bool verbose)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
@@ -1758,432 +1737,144 @@ void CPlayerInterface::initMovement( const TryMoveHero &details, const CGHeroIns
 {
 	auto subArr = (CGI->mh->ttiles)[hp.z];
 
-	if (details.end.x+1 == details.start.x && details.end.y+1 == details.start.y) //tl
-	{
-		//ho->moveDir = 1;
-		ho->isStanding = false;
-		subArr[hp.x-3][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, -31)));
-		subArr[hp.x-2][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 1, -31)));
-		subArr[hp.x-1][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 33, -31)));
-		subArr[hp.x][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 65, -31)));
-
-		subArr[hp.x-3][hp.y-1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, 1)));
-		subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, 1, 1), ho->id);
-		subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 33, 1), ho->id);
-		subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 65, 1), ho->id);
-
-		subArr[hp.x-3][hp.y].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, 33)));
-		subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, 1, 33), ho->id);
-		subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 33, 33), ho->id);
-		subRect(hp.x, hp.y, hp.z, genRect(32, 32, 65, 33), ho->id);
-
-		std::stable_sort(subArr[hp.x-3][hp.y-2].objects.begin(), subArr[hp.x-3][hp.y-2].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(subArr[hp.x-2][hp.y-2].objects.begin(), subArr[hp.x-2][hp.y-2].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(subArr[hp.x-1][hp.y-2].objects.begin(), subArr[hp.x-1][hp.y-2].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(subArr[hp.x][hp.y-2].objects.begin(), subArr[hp.x][hp.y-2].objects.end(), objectBlitOrderSorter);
-
-		std::stable_sort(subArr[hp.x-3][hp.y-1].objects.begin(), subArr[hp.x-3][hp.y-1].objects.end(), objectBlitOrderSorter);
-
-		std::stable_sort(subArr[hp.x-3][hp.y].objects.begin(), subArr[hp.x-3][hp.y].objects.end(), objectBlitOrderSorter);
-	}
-	else if (details.end.x == details.start.x && details.end.y+1 == details.start.y) //t
-	{
-		//ho->moveDir = 2;
-		ho->isStanding = false;
-		subArr[hp.x-2][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 0, -31)));
-		subArr[hp.x-1][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 32, -31)));
-		subArr[hp.x][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 64, -31)));
-
-		subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, 0, 1), ho->id);
-		subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 32, 1), ho->id);
-		subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 64, 1), ho->id);
-
-		subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, 0, 33), ho->id);
-		subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 32, 33), ho->id);
-		subRect(hp.x, hp.y, hp.z, genRect(32, 32, 64, 33), ho->id);
-
-		std::stable_sort(subArr[hp.x-2][hp.y-2].objects.begin(), subArr[hp.x-2][hp.y-2].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(subArr[hp.x-1][hp.y-2].objects.begin(), subArr[hp.x-1][hp.y-2].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(subArr[hp.x][hp.y-2].objects.begin(), subArr[hp.x][hp.y-2].objects.end(), objectBlitOrderSorter);
-	}
-	else if (details.end.x-1 == details.start.x && details.end.y+1 == details.start.y) //tr
-	{
-		//ho->moveDir = 3;
-		ho->isStanding = false;
-		subArr[hp.x-2][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -1, -31)));
-		subArr[hp.x-1][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 31, -31)));
-		subArr[hp.x][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 63, -31)));
-		subArr[hp.x+1][hp.y-2].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, -31)));
-
-		subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, -1, 1), ho->id);
-		subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 31, 1), ho->id);
-		subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 63, 1), ho->id);
-		subArr[hp.x+1][hp.y-1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, 1)));
-
-		subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, -1, 33), ho->id);
-		subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 31, 33), ho->id);
-		subRect(hp.x, hp.y, hp.z, genRect(32, 32, 63, 33), ho->id);
-		subArr[hp.x+1][hp.y].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, 33)));
-
-		std::stable_sort(subArr[hp.x-2][hp.y-2].objects.begin(), subArr[hp.x-2][hp.y-2].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(subArr[hp.x-1][hp.y-2].objects.begin(), subArr[hp.x-1][hp.y-2].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(subArr[hp.x][hp.y-2].objects.begin(), subArr[hp.x][hp.y-2].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(subArr[hp.x+1][hp.y-2].objects.begin(), subArr[hp.x+1][hp.y-2].objects.end(), objectBlitOrderSorter);
-
-		std::stable_sort(subArr[hp.x+1][hp.y-1].objects.begin(), subArr[hp.x+1][hp.y-1].objects.end(), objectBlitOrderSorter);
-
-		std::stable_sort(subArr[hp.x+1][hp.y].objects.begin(), subArr[hp.x+1][hp.y].objects.end(), objectBlitOrderSorter);
-	}
-	else if (details.end.x-1 == details.start.x && details.end.y == details.start.y) //r
-	{
-		//ho->moveDir = 4;
-		ho->isStanding = false;
-		subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, -1, 0), ho->id);
-		subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 31, 0), ho->id);
-		subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 63, 0), ho->id);
-		subArr[hp.x+1][hp.y-1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, 0)));
-
-		subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, -1, 32), ho->id);
-		subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 31, 32), ho->id);
-		subRect(hp.x, hp.y, hp.z, genRect(32, 32, 63, 32), ho->id);
-		subArr[hp.x+1][hp.y].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, 32)));
-
-		std::stable_sort(subArr[hp.x+1][hp.y-1].objects.begin(), subArr[hp.x+1][hp.y-1].objects.end(), objectBlitOrderSorter);
-
-		std::stable_sort(subArr[hp.x+1][hp.y].objects.begin(), subArr[hp.x+1][hp.y].objects.end(), objectBlitOrderSorter);
-	}
-	else if (details.end.x-1 == details.start.x && details.end.y-1 == details.start.y) //br
-	{
-		//ho->moveDir = 5;
-		ho->isStanding = false;
-		subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, -1, -1), ho->id);
-		subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 31, -1), ho->id);
-		subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 63, -1), ho->id);
-		subArr[hp.x+1][hp.y-1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, -1)));
-
-		subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, -1, 31), ho->id);
-		subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 31, 31), ho->id);
-		subRect(hp.x, hp.y, hp.z, genRect(32, 32, 63, 31), ho->id);
-		subArr[hp.x+1][hp.y].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, 31)));
-
-		subArr[hp.x-2][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -1, 63)));
-		subArr[hp.x-1][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 31, 63)));
-		subArr[hp.x][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 63, 63)));
-		subArr[hp.x+1][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 95, 63)));
-
-		std::stable_sort(subArr[hp.x+1][hp.y-1].objects.begin(), subArr[hp.x+1][hp.y-1].objects.end(), objectBlitOrderSorter);
+	ho->isStanding = false;
 
-		std::stable_sort(subArr[hp.x+1][hp.y].objects.begin(), subArr[hp.x+1][hp.y].objects.end(), objectBlitOrderSorter);
+	int heroWidth  = ho->appearance->getWidth();
+	int heroHeight = ho->appearance->getHeight();
 
-		std::stable_sort(subArr[hp.x-2][hp.y+1].objects.begin(), subArr[hp.x-2][hp.y+1].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(subArr[hp.x-1][hp.y+1].objects.begin(), subArr[hp.x-1][hp.y+1].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(subArr[hp.x][hp.y+1].objects.begin(), subArr[hp.x][hp.y+1].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(subArr[hp.x+1][hp.y+1].objects.begin(), subArr[hp.x+1][hp.y+1].objects.end(), objectBlitOrderSorter);
-	}
-	else if (details.end.x == details.start.x && details.end.y-1 == details.start.y) //b
-	{
-		//ho->moveDir = 6;
-		ho->isStanding = false;
-		subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, 0, -1), ho->id);
-		subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 32, -1), ho->id);
-		subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 64, -1), ho->id);
-
-		subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, 0, 31), ho->id);
-		subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 32, 31), ho->id);
-		subRect(hp.x, hp.y, hp.z, genRect(32, 32, 64, 31), ho->id);
-
-		subArr[hp.x-2][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 0, 63)));
-		subArr[hp.x-1][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 32, 63)));
-		subArr[hp.x][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 64, 63)));
+	int tileMinX = std::min(details.start.x, details.end.x) - heroWidth;
+	int tileMaxX = std::max(details.start.x, details.end.x);
+	int tileMinY = std::min(details.start.y, details.end.y) - heroHeight;
+	int tileMaxY = std::max(details.start.y, details.end.y);
 
-		std::stable_sort(subArr[hp.x-2][hp.y+1].objects.begin(), subArr[hp.x-2][hp.y+1].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(subArr[hp.x-1][hp.y+1].objects.begin(), subArr[hp.x-1][hp.y+1].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(subArr[hp.x][hp.y+1].objects.begin(), subArr[hp.x][hp.y+1].objects.end(), objectBlitOrderSorter);
-	}
-	else if (details.end.x+1 == details.start.x && details.end.y-1 == details.start.y) //bl
+	// determine tiles on which hero will be visible during movement and add hero as visible object on these tiles where necessary
+	for ( int tileX = tileMinX; tileX <= tileMaxX; ++tileX)
 	{
-		//ho->moveDir = 7;
-		ho->isStanding = false;
-		subArr[hp.x-3][hp.y-1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, -1)));
-		subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, 1, -1), ho->id);
-		subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 33, -1), ho->id);
-		subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 65, -1), ho->id);
-
-		subArr[hp.x-3][hp.y].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, 31)));
-		subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, 1, 31), ho->id);
-		subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 33, 31), ho->id);
-		subRect(hp.x, hp.y, hp.z, genRect(32, 32, 65, 31), ho->id);
-
-		subArr[hp.x-3][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, 63)));
-		subArr[hp.x-2][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 1, 63)));
-		subArr[hp.x-1][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 33, 63)));
-		subArr[hp.x][hp.y+1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, 65, 63)));
-
-		std::stable_sort(subArr[hp.x-3][hp.y-1].objects.begin(), subArr[hp.x-3][hp.y-1].objects.end(), objectBlitOrderSorter);
-
-		std::stable_sort(subArr[hp.x-3][hp.y].objects.begin(), subArr[hp.x-3][hp.y].objects.end(), objectBlitOrderSorter);
-
-		std::stable_sort(subArr[hp.x-3][hp.y+1].objects.begin(), subArr[hp.x-3][hp.y+1].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(subArr[hp.x-2][hp.y+1].objects.begin(), subArr[hp.x-2][hp.y+1].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(subArr[hp.x-1][hp.y+1].objects.begin(), subArr[hp.x-1][hp.y+1].objects.end(), objectBlitOrderSorter);
-		std::stable_sort(subArr[hp.x][hp.y+1].objects.begin(), subArr[hp.x][hp.y+1].objects.end(), objectBlitOrderSorter);
-	}
-	else if (details.end.x+1 == details.start.x && details.end.y == details.start.y) //l
-	{
-		//ho->moveDir = 8;
-		ho->isStanding = false;
-		subArr[hp.x-3][hp.y-1].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, 0)));
-		subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, 1, 0), ho->id);
-		subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 33, 0), ho->id);
-		subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 65, 0), ho->id);
-
-		subArr[hp.x-3][hp.y].objects.push_back(TerrainTileObject(ho, genRect(32, 32, -31, 32)));
-		subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, 1, 32), ho->id);
-		subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 33, 32), ho->id);
-		subRect(hp.x, hp.y, hp.z, genRect(32, 32, 65, 32), ho->id);
+		for ( int tileY = tileMinY; tileY <= tileMaxY; ++tileY)
+		{
+			bool heroVisibleHere = false;
+			auto & tile = subArr[tileX][tileY];
 
-		std::stable_sort(subArr[hp.x-3][hp.y-1].objects.begin(), subArr[hp.x-3][hp.y-1].objects.end(), objectBlitOrderSorter);
+			for ( auto const & obj : tile.objects)
+			{
+				if (obj.obj == ho)
+				{
+					heroVisibleHere = true;
+					break;
+				}
+			}
 
-		std::stable_sort(subArr[hp.x-3][hp.y].objects.begin(), subArr[hp.x-3][hp.y].objects.end(), objectBlitOrderSorter);
+			if ( !heroVisibleHere)
+			{
+				tile.objects.push_back(TerrainTileObject(ho, {0,0,32,32}));
+				std::stable_sort(tile.objects.begin(), tile.objects.end(), objectBlitOrderSorter);
+			}
+		}
 	}
 }
 
 void CPlayerInterface::movementPxStep( const TryMoveHero &details, int i, const int3 &hp, const CGHeroInstance * ho )
 {
-	if (details.end.x+1 == details.start.x && details.end.y+1 == details.start.y) //tl
-	{
-		//setting advmap shift
-		adventureInt->terrain.moveX = i-32;
-		adventureInt->terrain.moveY = i-32;
-
-		subRect(hp.x-3, hp.y-2, hp.z, genRect(32, 32, -31+i, -31+i), ho->id);
-		subRect(hp.x-2, hp.y-2, hp.z, genRect(32, 32, 1+i, -31+i), ho->id);
-		subRect(hp.x-1, hp.y-2, hp.z, genRect(32, 32, 33+i, -31+i), ho->id);
-		subRect(hp.x, hp.y-2, hp.z, genRect(32, 32, 65+i, -31+i), ho->id);
-
-		subRect(hp.x-3, hp.y-1, hp.z, genRect(32, 32, -31+i, 1+i), ho->id);
-		subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, 1+i, 1+i), ho->id);
-		subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 33+i, 1+i), ho->id);
-		subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 65+i, 1+i), ho->id);
-
-		subRect(hp.x-3, hp.y, hp.z, genRect(32, 32, -31+i, 33+i), ho->id);
-		subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, 1+i, 33+i), ho->id);
-		subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 33+i, 33+i), ho->id);
-		subRect(hp.x, hp.y, hp.z, genRect(32, 32, 65+i, 33+i), ho->id);
-	}
-	else if (details.end.x == details.start.x && details.end.y+1 == details.start.y) //t
-	{
-		//setting advmap shift
-		adventureInt->terrain.moveY = i-32;
-
-		subRect(hp.x-2, hp.y-2, hp.z, genRect(32, 32, 0, -31+i), ho->id);
-		subRect(hp.x-1, hp.y-2, hp.z, genRect(32, 32, 32, -31+i), ho->id);
-		subRect(hp.x, hp.y-2, hp.z, genRect(32, 32, 64, -31+i), ho->id);
-
-		subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, 0, 1+i), ho->id);
-		subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 32, 1+i), ho->id);
-		subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 64, 1+i), ho->id);
-
-		subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, 0, 33+i), ho->id);
-		subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 32, 33+i), ho->id);
-		subRect(hp.x, hp.y, hp.z, genRect(32, 32, 64, 33+i), ho->id);
-	}
-	else if (details.end.x-1 == details.start.x && details.end.y+1 == details.start.y) //tr
-	{
-		//setting advmap shift
-		adventureInt->terrain.moveX = -i+32;
-		adventureInt->terrain.moveY = i-32;
-
-		subRect(hp.x-2, hp.y-2, hp.z, genRect(32, 32, -1-i, -31+i), ho->id);
-		subRect(hp.x-1, hp.y-2, hp.z, genRect(32, 32, 31-i, -31+i), ho->id);
-		subRect(hp.x, hp.y-2, hp.z, genRect(32, 32, 63-i, -31+i), ho->id);
-		subRect(hp.x+1, hp.y-2, hp.z, genRect(32, 32, 95-i, -31+i), ho->id);
+	auto subArr = (CGI->mh->ttiles)[hp.z];
 
-		subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, -1-i, 1+i), ho->id);
-		subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 31-i, 1+i), ho->id);
-		subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 63-i, 1+i), ho->id);
-		subRect(hp.x+1, hp.y-1, hp.z, genRect(32, 32, 95-i, 1+i), ho->id);
+	int heroWidth  = ho->appearance->getWidth();
+	int heroHeight = ho->appearance->getHeight();
 
-		subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, -1-i, 33+i), ho->id);
-		subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 31-i, 33+i), ho->id);
-		subRect(hp.x, hp.y, hp.z, genRect(32, 32, 63-i, 33+i), ho->id);
-		subRect(hp.x+1, hp.y, hp.z, genRect(32, 32, 95-i, 33+i), ho->id);
-	}
-	else if (details.end.x-1 == details.start.x && details.end.y == details.start.y) //r
-	{
-		//setting advmap shift
-		adventureInt->terrain.moveX = -i+32;
+	int tileMinX = std::min(details.start.x, details.end.x) - heroWidth;
+	int tileMaxX = std::max(details.start.x, details.end.x);
+	int tileMinY = std::min(details.start.y, details.end.y) - heroHeight;
+	int tileMaxY = std::max(details.start.y, details.end.y);
 
-		subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, -1-i, 0), ho->id);
-		subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 31-i, 0), ho->id);
-		subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 63-i, 0), ho->id);
-		subRect(hp.x+1, hp.y-1, hp.z, genRect(32, 32, 95-i, 0), ho->id);
+	std::shared_ptr<CAnimation> animation = graphics->getAnimation(ho);
 
-		subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, -1-i, 32), ho->id);
-		subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 31-i, 32), ho->id);
-		subRect(hp.x, hp.y, hp.z, genRect(32, 32, 63-i, 32), ho->id);
-		subRect(hp.x+1, hp.y, hp.z, genRect(32, 32, 95-i, 32), ho->id);
-	}
-	else if (details.end.x-1 == details.start.x && details.end.y-1 == details.start.y) //br
-	{
+	assert(animation);
+	assert(animation->size(0) != 0);
+	auto image = animation->getImage(0,0);
 
-		//setting advmap shift
-		adventureInt->terrain.moveX = -i+32;
-		adventureInt->terrain.moveY = -i+32;
+	int heroImageOldX = details.start.x * 32;
+	int heroImageOldY = details.start.y * 32;
 
-		subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, -1-i, -1-i), ho->id);
-		subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 31-i, -1-i), ho->id);
-		subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 63-i, -1-i), ho->id);
-		subRect(hp.x+1, hp.y-1, hp.z, genRect(32, 32, 95-i, -1-i), ho->id);
+	int heroImageNewX = details.end.x * 32;
+	int heroImageNewY = details.end.y * 32;
 
-		subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, -1-i, 31-i), ho->id);
-		subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 31-i, 31-i), ho->id);
-		subRect(hp.x, hp.y, hp.z, genRect(32, 32, 63-i, 31-i), ho->id);
-		subRect(hp.x+1, hp.y, hp.z, genRect(32, 32, 95-i, 31-i), ho->id);
+	int heroImageCurrX = heroImageOldX + i*(heroImageNewX - heroImageOldX)/32;
+	int heroImageCurrY = heroImageOldY + i*(heroImageNewY - heroImageOldY)/32;
 
-		subRect(hp.x-2, hp.y+1, hp.z, genRect(32, 32, -1-i, 63-i), ho->id);
-		subRect(hp.x-1, hp.y+1, hp.z, genRect(32, 32, 31-i, 63-i), ho->id);
-		subRect(hp.x, hp.y+1, hp.z, genRect(32, 32, 63-i, 63-i), ho->id);
-		subRect(hp.x+1, hp.y+1, hp.z, genRect(32, 32, 95-i, 63-i), ho->id);
-	}
-	else if (details.end.x == details.start.x && details.end.y-1 == details.start.y) //b
+	// recompute which part of hero sprite will be visible on each tile at this point of movement animation
+	for ( int tileX = tileMinX; tileX <= tileMaxX; ++tileX)
 	{
-		//setting advmap shift
-		adventureInt->terrain.moveY = -i+32;
-
-		subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, 0, -1-i), ho->id);
-		subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 32, -1-i), ho->id);
-		subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 64, -1-i), ho->id);
-
-		subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, 0, 31-i), ho->id);
-		subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 32, 31-i), ho->id);
-		subRect(hp.x, hp.y, hp.z, genRect(32, 32, 64, 31-i), ho->id);
-
-		subRect(hp.x-2, hp.y+1, hp.z, genRect(32, 32, 0, 63-i), ho->id);
-		subRect(hp.x-1, hp.y+1, hp.z, genRect(32, 32, 32, 63-i), ho->id);
-		subRect(hp.x, hp.y+1, hp.z, genRect(32, 32, 64, 63-i), ho->id);
-	}
-	else if (details.end.x+1 == details.start.x && details.end.y-1 == details.start.y) //bl
-	{
-		//setting advmap shift
-		adventureInt->terrain.moveX = i-32;
-		adventureInt->terrain.moveY = -i+32;
-
-		subRect(hp.x-3, hp.y-1, hp.z, genRect(32, 32, -31+i, -1-i), ho->id);
-		subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, 1+i, -1-i), ho->id);
-		subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 33+i, -1-i), ho->id);
-		subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 65+i, -1-i), ho->id);
-
-		subRect(hp.x-3, hp.y, hp.z, genRect(32, 32, -31+i, 31-i), ho->id);
-		subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, 1+i, 31-i), ho->id);
-		subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 33+i, 31-i), ho->id);
-		subRect(hp.x, hp.y, hp.z, genRect(32, 32, 65+i, 31-i), ho->id);
+		for ( int tileY = tileMinY; tileY <= tileMaxY; ++tileY)
+		{
+			auto & tile = subArr[tileX][tileY];
+			for ( auto & obj : tile.objects)
+			{
+				if (obj.obj == ho)
+				{
+					int tilePosX = tileX * 32;
+					int tilePosY = tileY * 32;
 
-		subRect(hp.x-3, hp.y+1, hp.z, genRect(32, 32, -31+i, 63-i), ho->id);
-		subRect(hp.x-2, hp.y+1, hp.z, genRect(32, 32, 1+i, 63-i), ho->id);
-		subRect(hp.x-1, hp.y+1, hp.z, genRect(32, 32, 33+i, 63-i), ho->id);
-		subRect(hp.x, hp.y+1, hp.z, genRect(32, 32, 65+i, 63-i), ho->id);
+					obj.rect.x = tilePosX - heroImageCurrX + image->width() - 32;
+					obj.rect.y = tilePosY - heroImageCurrY + image->height() - 32;
+				}
+			}
+		}
 	}
-	else if (details.end.x+1 == details.start.x && details.end.y == details.start.y) //l
-	{
-		//setting advmap shift
-		adventureInt->terrain.moveX = i-32;
-
-		subRect(hp.x-3, hp.y-1, hp.z, genRect(32, 32, -31+i, 0), ho->id);
-		subRect(hp.x-2, hp.y-1, hp.z, genRect(32, 32, 1+i, 0), ho->id);
-		subRect(hp.x-1, hp.y-1, hp.z, genRect(32, 32, 33+i, 0), ho->id);
-		subRect(hp.x, hp.y-1, hp.z, genRect(32, 32, 65+i, 0), ho->id);
 
-		subRect(hp.x-3, hp.y, hp.z, genRect(32, 32, -31+i, 32), ho->id);
-		subRect(hp.x-2, hp.y, hp.z, genRect(32, 32, 1+i, 32), ho->id);
-		subRect(hp.x-1, hp.y, hp.z, genRect(32, 32, 33+i, 32), ho->id);
-		subRect(hp.x, hp.y, hp.z, genRect(32, 32, 65+i, 32), ho->id);
-	}
+	adventureInt->terrain.moveX = (32 - i) * (heroImageNewX - heroImageOldX) / 32;
+	adventureInt->terrain.moveY = (32 - i) * (heroImageNewY - heroImageOldY) / 32;
 }
 
 void CPlayerInterface::finishMovement( const TryMoveHero &details, const int3 &hp, const CGHeroInstance * ho )
 {
-	adventureInt->terrain.moveX = adventureInt->terrain.moveY = 0;
+	auto subArr = (CGI->mh->ttiles)[hp.z];
 
-	if (details.end.x+1 == details.start.x && details.end.y+1 == details.start.y) //tl
-	{
-		delObjRect(hp.x, hp.y-2, hp.z, ho->id);
-		delObjRect(hp.x, hp.y-1, hp.z, ho->id);
-		delObjRect(hp.x, hp.y, hp.z, ho->id);
-		delObjRect(hp.x-1, hp.y, hp.z, ho->id);
-		delObjRect(hp.x-2, hp.y, hp.z, ho->id);
-		delObjRect(hp.x-3, hp.y, hp.z, ho->id);
-	}
-	else if (details.end.x == details.start.x && details.end.y+1 == details.start.y) //t
-	{
-		delObjRect(hp.x, hp.y, hp.z, ho->id);
-		delObjRect(hp.x-1, hp.y, hp.z, ho->id);
-		delObjRect(hp.x-2, hp.y, hp.z, ho->id);
-	}
-	else if (details.end.x-1 == details.start.x && details.end.y+1 == details.start.y) //tr
-	{
-		delObjRect(hp.x-2, hp.y-2, hp.z, ho->id);
-		delObjRect(hp.x-2, hp.y-1, hp.z, ho->id);
-		delObjRect(hp.x+1, hp.y, hp.z, ho->id);
-		delObjRect(hp.x, hp.y, hp.z, ho->id);
-		delObjRect(hp.x-1, hp.y, hp.z, ho->id);
-		delObjRect(hp.x-2, hp.y, hp.z, ho->id);
-	}
-	else if (details.end.x-1 == details.start.x && details.end.y == details.start.y) //r
-	{
-		delObjRect(hp.x-2, hp.y-1, hp.z, ho->id);
-		delObjRect(hp.x-2, hp.y, hp.z, ho->id);
-	}
-	else if (details.end.x-1 == details.start.x && details.end.y-1 == details.start.y) //br
-	{
-		delObjRect(hp.x-2, hp.y+1, hp.z, ho->id);
-		delObjRect(hp.x-2, hp.y, hp.z, ho->id);
-		delObjRect(hp.x+1, hp.y-1, hp.z, ho->id);
-		delObjRect(hp.x, hp.y-1, hp.z, ho->id);
-		delObjRect(hp.x-1, hp.y-1, hp.z, ho->id);
-		delObjRect(hp.x-2, hp.y-1, hp.z, ho->id);
-	}
-	else if (details.end.x == details.start.x && details.end.y-1 == details.start.y) //b
+	int heroWidth  = ho->appearance->getWidth();
+	int heroHeight = ho->appearance->getHeight();
+
+	int tileMinX = std::min(details.start.x, details.end.x) - heroWidth;
+	int tileMaxX = std::max(details.start.x, details.end.x);
+	int tileMinY = std::min(details.start.y, details.end.y) - heroHeight;
+	int tileMaxY = std::max(details.start.y, details.end.y);
+
+	// erase hero from all tiles on which he is currently visible
+	for ( int tileX = tileMinX; tileX <= tileMaxX; ++tileX)
 	{
-		delObjRect(hp.x, hp.y-1, hp.z, ho->id);
-		delObjRect(hp.x-1, hp.y-1, hp.z, ho->id);
-		delObjRect(hp.x-2, hp.y-1, hp.z, ho->id);
+		for ( int tileY = tileMinY; tileY <= tileMaxY; ++tileY)
+		{
+			auto & tile = subArr[tileX][tileY];
+			for (size_t i = 0; i < tile.objects.size(); ++i)
+			{
+				if ( tile.objects[i].obj == ho)
+				{
+					tile.objects.erase(tile.objects.begin() + i);
+					break;
+				}
+			}
+		}
 	}
-	else if (details.end.x+1 == details.start.x && details.end.y-1 == details.start.y) //bl
+
+	// re-add hero to all tiles on which he will still be visible after animation is over
+	for ( int tileX = details.end.x - heroWidth + 1; tileX <= details.end.x; ++tileX)
 	{
-		delObjRect(hp.x, hp.y-1, hp.z, ho->id);
-		delObjRect(hp.x-1, hp.y-1, hp.z, ho->id);
-		delObjRect(hp.x-2, hp.y-1, hp.z, ho->id);
-		delObjRect(hp.x-3, hp.y-1, hp.z, ho->id);
-		delObjRect(hp.x, hp.y, hp.z, ho->id);
-		delObjRect(hp.x, hp.y+1, hp.z, ho->id);
+		for ( int tileY = details.end.y - heroHeight + 1; tileY <= details.end.y; ++tileY)
+		{
+			auto & tile = subArr[tileX][tileY];
+			tile.objects.push_back(TerrainTileObject(ho, {0,0,32,32}));
+		}
 	}
-	else if (details.end.x+1 == details.start.x && details.end.y == details.start.y) //l
+
+	// update object list on all tiles that were affected during previous operations
+	for ( int tileX = tileMinX; tileX <= tileMaxX; ++tileX)
 	{
-		delObjRect(hp.x, hp.y-1, hp.z, ho->id);
-		delObjRect(hp.x, hp.y, hp.z, ho->id);
+		for ( int tileY = tileMinY; tileY <= tileMaxY; ++tileY)
+		{
+			auto & tile = subArr[tileX][tileY];
+			std::stable_sort(tile.objects.begin(), tile.objects.end(), objectBlitOrderSorter);
+		}
 	}
 
-	//restoring good rects
-	subRect(details.end.x-2, details.end.y-1, details.end.z, genRect(32, 32, 0, 0), ho->id);
-	subRect(details.end.x-1, details.end.y-1, details.end.z, genRect(32, 32, 32, 0), ho->id);
-	subRect(details.end.x, details.end.y-1, details.end.z, genRect(32, 32, 64, 0), ho->id);
-
-	subRect(details.end.x-2, details.end.y, details.end.z, genRect(32, 32, 0, 32), ho->id);
-	subRect(details.end.x-1, details.end.y, details.end.z, genRect(32, 32, 32, 32), ho->id);
-	subRect(details.end.x, details.end.y, details.end.z, genRect(32, 32, 64, 32), ho->id);
-
-	//restoring good order of objects
-
-	boost::detail::multi_array::sub_array<TerrainTile2, 2> subArr = (CGI->mh->ttiles)[details.end.z];
-
-	std::stable_sort(subArr[details.end.x-2][details.end.y-1].objects.begin(), subArr[details.end.x-2][details.end.y-1].objects.end(), objectBlitOrderSorter);
-	std::stable_sort(subArr[details.end.x-1][details.end.y-1].objects.begin(), subArr[details.end.x-1][details.end.y-1].objects.end(), objectBlitOrderSorter);
-	std::stable_sort(subArr[details.end.x][details.end.y-1].objects.begin(), subArr[details.end.x][details.end.y-1].objects.end(), objectBlitOrderSorter);
-
-	std::stable_sort(subArr[details.end.x-2][details.end.y].objects.begin(), subArr[details.end.x-2][details.end.y].objects.end(), objectBlitOrderSorter);
-	std::stable_sort(subArr[details.end.x-1][details.end.y].objects.begin(), subArr[details.end.x-1][details.end.y].objects.end(), objectBlitOrderSorter);
-	std::stable_sort(subArr[details.end.x][details.end.y].objects.begin(), subArr[details.end.x][details.end.y].objects.end(), objectBlitOrderSorter);
+	//recompute hero sprite positioning using hero's final position
+	movementPxStep(details, 32, hp, ho);
 }
 
 void CPlayerInterface::gameOver(PlayerColor player, const EVictoryLossCheckResult & victoryLossCheckResult )

+ 40 - 23
client/battle/CBattleAnimations.cpp

@@ -785,22 +785,25 @@ bool CShootingAnimation::init()
 	if (projectileAngle > straightAngle)
 	{
 		//upper shot
-		spi.x = fromPos.x + 222 + ( -25 + shooterInfo->animation.upperRightMissleOffsetX ) * multiplier;
-		spi.y = fromPos.y + 265 + shooterInfo->animation.upperRightMissleOffsetY;
+		spi.x0 = fromPos.x + 222 + ( -25 + shooterInfo->animation.upperRightMissleOffsetX ) * multiplier;
+		spi.y0 = fromPos.y + 265 + shooterInfo->animation.upperRightMissleOffsetY;
 	}
 	else if (projectileAngle < -straightAngle)
 	{
 		//lower shot
-		spi.x = fromPos.x + 222 + ( -25 + shooterInfo->animation.lowerRightMissleOffsetX ) * multiplier;
-		spi.y = fromPos.y + 265 + shooterInfo->animation.lowerRightMissleOffsetY;
+		spi.x0 = fromPos.x + 222 + ( -25 + shooterInfo->animation.lowerRightMissleOffsetX ) * multiplier;
+		spi.y0 = fromPos.y + 265 + shooterInfo->animation.lowerRightMissleOffsetY;
 	}
 	else
 	{
 		//straight shot
-		spi.x = fromPos.x + 222 + ( -25 + shooterInfo->animation.rightMissleOffsetX ) * multiplier;
-		spi.y = fromPos.y + 265 + shooterInfo->animation.rightMissleOffsetY;
+		spi.x0 = fromPos.x + 222 + ( -25 + shooterInfo->animation.rightMissleOffsetX ) * multiplier;
+		spi.y0 = fromPos.y + 265 + shooterInfo->animation.rightMissleOffsetY;
 	}
 
+	spi.x = spi.x0;
+	spi.y = spi.y0;
+
 	destPos += Point(225, 225);
 
 	// recalculate angle taking in account offsets
@@ -811,7 +814,9 @@ bool CShootingAnimation::init()
 	if (attackedStack)
 	{
 		double animSpeed = AnimationControls::getProjectileSpeed(); // flight speed of projectile
-		spi.lastStep = static_cast<int>(sqrt(static_cast<double>((destPos.x - spi.x) * (destPos.x - spi.x) + (destPos.y - spi.y) * (destPos.y - spi.y))) / animSpeed);
+		double distanceSquared = (destPos.x - spi.x) * (destPos.x - spi.x) + (destPos.y - spi.y) * (destPos.y - spi.y);
+		double distance = sqrt(distanceSquared);
+		spi.lastStep = std::round(distance / animSpeed);
 		if(spi.lastStep == 0)
 			spi.lastStep = 1;
 		spi.dx = (destPos.x - spi.x) / spi.lastStep;
@@ -837,30 +842,42 @@ bool CShootingAnimation::init()
 	}
 	double pi = boost::math::constants::pi<double>();
 
-	if (owner->idToProjectile.count(spi.creID) == 0) //in some cases (known one: hero grants shooter bonus to unit) the shooter stack's projectile may not be properly initialized
+	//in some cases (known one: hero grants shooter bonus to unit) the shooter stack's projectile may not be properly initialized
+	if (!owner->idToProjectile.count(spi.creID) && !owner->idToRay.count(spi.creID))
 		owner->initStackProjectile(shooter);
 
-	// only frames below maxFrame are usable: anything  higher is either no present or we don't know when it should be used
-	size_t maxFrame = std::min<size_t>(angles.size(), owner->idToProjectile.at(spi.creID)->size(0));
+	if (owner->idToProjectile.count(spi.creID))
+	{
+		// only frames below maxFrame are usable: anything  higher is either no present or we don't know when it should be used
+		size_t maxFrame = std::min<size_t>(angles.size(), owner->idToProjectile.at(spi.creID)->size(0));
 
-	assert(maxFrame > 0);
+		assert(maxFrame > 0);
 
-	// values in angles array indicate position from which this frame was rendered, in degrees.
-	// find frame that has closest angle to one that we need for this shot
-	size_t bestID = 0;
-	double bestDiff = fabs( angles[0] / 180 * pi - projectileAngle );
+		// values in angles array indicate position from which this frame was rendered, in degrees.
+		// find frame that has closest angle to one that we need for this shot
+		size_t bestID = 0;
+		double bestDiff = fabs( angles[0] / 180 * pi - projectileAngle );
 
-	for (size_t i=1; i<maxFrame; i++)
-	{
-		double currentDiff = fabs( angles[i] / 180 * pi - projectileAngle );
-		if (currentDiff < bestDiff)
+		for (size_t i=1; i<maxFrame; i++)
 		{
-			bestID = i;
-			bestDiff = currentDiff;
+			double currentDiff = fabs( angles[i] / 180 * pi - projectileAngle );
+			if (currentDiff < bestDiff)
+			{
+				bestID = i;
+				bestDiff = currentDiff;
+			}
 		}
-	}
 
-	spi.frameNum = static_cast<int>(bestID);
+		spi.frameNum = static_cast<int>(bestID);
+	}
+	else if (owner->idToRay.count(spi.creID))
+	{
+		// no-op
+	}
+	else
+	{
+		logGlobal->error("Unable to find valid projectile for shooter %d", spi.creID);
+	}
 
 	// Set projectile animation start delay which is specified in frames
 	spi.animStartDelay = shooterInfo->animation.attackClimaxFrame;

+ 1 - 0
client/battle/CBattleAnimations.h

@@ -189,6 +189,7 @@ public:
 /// Small struct which contains information about the position and the velocity of a projectile
 struct ProjectileInfo
 {
+	double x0, y0; //initial position on the screen
 	double x, y; //position on the screen
 	double dx, dy; //change in position in one step
 	int step, lastStep; //to know when finish showing this projectile

+ 64 - 17
client/battle/CBattleInterface.cpp

@@ -1020,15 +1020,22 @@ void CBattleInterface::initStackProjectile(const CStack * stack)
 	else
 		creature = stack->getCreature();
 
-	std::shared_ptr<CAnimation> projectile = std::make_shared<CAnimation>(creature->animation.projectileImageName);
-	projectile->preload();
+	if (creature->animation.projectileRay.empty())
+	{
+		std::shared_ptr<CAnimation> projectile = std::make_shared<CAnimation>(creature->animation.projectileImageName);
+		projectile->preload();
 
-	if(projectile->size(1) != 0)
-		logAnim->error("Expected empty group 1 in stack projectile");
-	else
-		projectile->createFlippedGroup(0, 1);
+		if(projectile->size(1) != 0)
+			logAnim->error("Expected empty group 1 in stack projectile");
+		else
+			projectile->createFlippedGroup(0, 1);
 
-	idToProjectile[stack->getCreature()->idNumber] = projectile;
+		idToProjectile[stack->getCreature()->idNumber] = projectile;
+	}
+	else
+	{
+		idToRay[stack->getCreature()->idNumber] = creature->animation.projectileRay;
+	}
 }
 
 void CBattleInterface::stackRemoved(uint32_t stackID)
@@ -3213,23 +3220,63 @@ void CBattleInterface::showProjectiles(SDL_Surface *to)
 				continue; // wait...
 		}
 
-		size_t group = it->reverse ? 1 : 0;
-		auto image = idToProjectile[it->creID]->getImage(it->frameNum, group, true);
+		if (idToProjectile.count(it->creID))
+		{
+			size_t group = it->reverse ? 1 : 0;
+			auto image = idToProjectile[it->creID]->getImage(it->frameNum, group, true);
+
+			if(image)
+			{
+				SDL_Rect dst;
+				dst.h = image->height();
+				dst.w = image->width();
+				dst.x = static_cast<int>(it->x - dst.w / 2);
+				dst.y = static_cast<int>(it->y - dst.h / 2);
 
-		if(image)
+				image->draw(to, &dst, nullptr);
+			}
+		}
+		if (idToRay.count(it->creID))
 		{
-			SDL_Rect dst;
-			dst.h = image->height();
-			dst.w = image->width();
-			dst.x = static_cast<int>(it->x - dst.w / 2);
-			dst.y = static_cast<int>(it->y - dst.h / 2);
+			auto const & ray = idToRay[it->creID];
+
+			if (std::abs(it->dx) > std::abs(it->dy)) // draw in horizontal axis
+			{
+				int y1 =  it->y0 - ray.size() / 2;
+				int y2 =  it->y - ray.size() / 2;
+
+				int x1 = it->x0;
+				int x2 = it->x;
+
+				for (size_t i = 0; i < ray.size(); ++i)
+				{
+					SDL_Color beginColor{ ray[i].r1, ray[i].g1, ray[i].b1, ray[i].a1};
+					SDL_Color endColor  { ray[i].r2, ray[i].g2, ray[i].b2, ray[i].a2};
 
-			image->draw(to, &dst, nullptr);
+					CSDL_Ext::drawLine(to, x1, y1 + i, x2, y2 + i, beginColor, endColor);
+				}
+			}
+			else // draw in vertical axis
+			{
+				int x1 = it->x0 - ray.size() / 2;
+				int x2 = it->x - ray.size() / 2;
+
+				int y1 =  it->y0;
+				int y2 =  it->y;
+
+				for (size_t i = 0; i < ray.size(); ++i)
+				{
+					SDL_Color beginColor{ ray[i].r1, ray[i].g1, ray[i].b1, ray[i].a1};
+					SDL_Color endColor  { ray[i].r2, ray[i].g2, ray[i].b2, ray[i].a2};
+
+					CSDL_Ext::drawLine(to, x1 + i, y1, x2 + i, y2, beginColor, endColor);
+				}
+			}
 		}
 
 		// Update projectile
 		++it->step;
-		if (it->step == it->lastStep)
+		if (it->step > it->lastStep)
 		{
 			toBeDeleted.insert(toBeDeleted.end(), it);
 		}

+ 2 - 0
client/battle/CBattleInterface.h

@@ -17,6 +17,7 @@
 #include "CBattleAnimations.h"
 
 #include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation
+#include "../../lib/CCreatureHandler.h"
 #include "../../lib/battle/CBattleInfoCallback.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
@@ -148,6 +149,7 @@ private:
 	std::map<int32_t, std::shared_ptr<CCreatureAnimation>> creAnims; //animations of creatures from fighting armies (order by BattleInfo's stacks' ID)
 
 	std::map<int, std::shared_ptr<CAnimation>> idToProjectile;
+	std::map<int, std::vector<CCreature::CreatureAnimation::RayColor>> idToRay;
 
 	std::map<std::string, std::shared_ptr<CAnimation>> animationsCache;
 	std::map<si32, std::shared_ptr<CAnimation>> obstacleAnimations;

+ 69 - 0
client/gui/SDL_Extensions.cpp

@@ -361,6 +361,75 @@ void CSDL_Ext::update(SDL_Surface * what)
 	if(0 !=SDL_UpdateTexture(screenTexture, nullptr, what->pixels, what->pitch))
 		logGlobal->error("%s SDL_UpdateTexture %s", __FUNCTION__, SDL_GetError());
 }
+
+template<typename Int>
+Int lerp(Int a, Int b, float f)
+{
+	return a + std::round((b - a) * f);
+}
+
+static void drawLineX(SDL_Surface * sur, int x1, int y1, int x2, int y2, const SDL_Color & color1, const SDL_Color & color2)
+{
+	for(int x = x1; x <= x2; x++)
+	{
+		float f = float(x - x1) / float(x2 - x1);
+		int y = lerp(y1, y2, f);
+
+		uint8_t r = lerp(color1.r, color2.r, f);
+		uint8_t g = lerp(color1.g, color2.g, f);
+		uint8_t b = lerp(color1.b, color2.b, f);
+		uint8_t a = lerp(color1.a, color2.a, f);
+
+		Uint8 *p = CSDL_Ext::getPxPtr(sur, x, y);
+		ColorPutter<4, 0>::PutColor(p, r,g,b,a);
+	}
+}
+
+static void drawLineY(SDL_Surface * sur, int x1, int y1, int x2, int y2, const SDL_Color & color1, const SDL_Color & color2)
+{
+	for(int y = y1; y <= y2; y++)
+	{
+		float f = float(y - y1) / float(y2 - y1);
+		int x = lerp(x1, x2, f);
+
+		uint8_t r = lerp(color1.r, color2.r, f);
+		uint8_t g = lerp(color1.g, color2.g, f);
+		uint8_t b = lerp(color1.b, color2.b, f);
+		uint8_t a = lerp(color1.a, color2.a, f);
+
+		Uint8 *p = CSDL_Ext::getPxPtr(sur, x, y);
+		ColorPutter<4, 0>::PutColor(p, r,g,b,a);
+	}
+}
+
+void CSDL_Ext::drawLine(SDL_Surface * sur, int x1, int y1, int x2, int y2, const SDL_Color & color1, const SDL_Color & color2)
+{
+	int width  = std::abs(x1-x2);
+	int height = std::abs(y1-y2);
+
+	if ( width == 0 && height == 0)
+	{
+		Uint8 *p = CSDL_Ext::getPxPtr(sur, x1, y1);
+		ColorPutter<4, 0>::PutColorAlpha(p, color1);
+		return;
+	}
+
+	if (width > height)
+	{
+		if ( x1 < x2)
+			drawLineX(sur, x1,y1,x2,y2, color1, color2);
+		else
+			drawLineX(sur, x2,y2,x1,y1, color2, color1);
+	}
+	else
+	{
+		if ( y1 < y2)
+			drawLineY(sur, x1,y1,x2,y2, color1, color2);
+		else
+			drawLineY(sur, x2,y2,x1,y1, color2, color1);
+	}
+}
+
 void CSDL_Ext::drawBorder(SDL_Surface * sur, int x, int y, int w, int h, const int3 &color)
 {
 	for(int i = 0; i < w; i++)

+ 1 - 0
client/gui/SDL_Extensions.h

@@ -236,6 +236,7 @@ namespace CSDL_Ext
 	SDL_Color makeColor(ui8 r, ui8 g, ui8 b, ui8 a);
 
 	void update(SDL_Surface * what = screen); //updates whole surface (default - main screen)
+	void drawLine(SDL_Surface * sur, int x1, int y1, int x2, int y2, const SDL_Color & color1, const SDL_Color & color2);
 	void drawBorder(SDL_Surface * sur, int x, int y, int w, int h, const int3 &color);
 	void drawBorder(SDL_Surface * sur, const SDL_Rect &r, const int3 &color);
 	void drawDashedBorder(SDL_Surface * sur, const Rect &r, const int3 &color);

+ 4 - 0
client/lobby/RandomMapTab.cpp

@@ -107,6 +107,10 @@ RandomMapTab::RandomMapTab()
 	groupCompOnlyPlayers->addCallback([&](int btnId)
 	{
 		mapGenOptions->setCompOnlyPlayerCount(btnId);
+		
+		// deactive some MaxPlayers buttons to prevent total number of players exceeds PlayerColor::PLAYER_LIMIT_I
+		deactivateButtonsFrom(groupMaxPlayers.get(), PlayerColor::PLAYER_LIMIT_I - btnId + 1);
+		
 		deactivateButtonsFrom(groupCompOnlyTeams.get(), (btnId == 0 ? 1 : btnId));
 		validateCompOnlyPlayersCnt(btnId);
 		updateMapInfoByHost();

+ 16 - 2
config/creatures/dungeon.json

@@ -135,7 +135,14 @@
 			"animation": "CBEHOL.DEF",
 			"missile" :
 			{
-				"projectile": "SMBALX.DEF"
+				"ray" :
+				[
+					{ "start" : [ 160, 160, 160, 255 ], "end" : [ 160, 160, 160,  64 ] },
+					{ "start" : [ 192, 192, 192, 255 ], "end" : [ 192, 192, 192, 128 ] },
+					{ "start" : [ 224, 224, 224, 255 ], "end" : [ 224, 224, 224, 255 ] },
+					{ "start" : [ 192, 192, 192, 255 ], "end" : [ 192, 192, 192, 128 ] },
+					{ "start" : [ 160, 160, 160, 255 ], "end" : [ 160, 160, 160,  64 ] }
+				]
 			}
 		},
 		"sound" :
@@ -158,7 +165,14 @@
 			"animation": "CEVEYE.DEF",
 			"missile" :
 			{
-				"projectile": "SMBALX.DEF"
+				"ray" :
+				[
+					{ "start" : [ 160, 160, 160, 255 ], "end" : [ 160, 160, 160,  64 ] },
+					{ "start" : [ 192, 192, 192, 255 ], "end" : [ 192, 192, 192, 128 ] },
+					{ "start" : [ 224, 224, 224, 255 ], "end" : [ 224, 224, 224, 255 ] },
+					{ "start" : [ 192, 192, 192, 255 ], "end" : [ 192, 192, 192, 128 ] },
+					{ "start" : [ 160, 160, 160, 255 ], "end" : [ 160, 160, 160,  64 ] }
+				]
 			}
 		},
 		"sound" :

+ 9 - 1
config/creatures/tower.json

@@ -208,7 +208,15 @@
 			"animation": "CAMAGE.DEF",
 			"missile" :
 			{
-				"projectile": "PMAGEX.DEF"
+				"attackClimaxFrame" : 8,
+				"ray" :
+				[
+					{ "start" : [ 160, 192,   0, 255 ], "end" : [ 160, 192,   0,  64 ] },
+					{ "start" : [ 128, 224, 128, 255 ], "end" : [ 128, 224, 128, 128 ] },
+					{ "start" : [  32, 176,  32, 255 ], "end" : [  32, 176,  32, 255 ] },
+					{ "start" : [ 128, 224, 128, 255 ], "end" : [ 128, 224, 128, 128 ] },
+					{ "start" : [ 160, 192,   0, 255 ], "end" : [ 160, 192,   0,  64 ] }
+				]
 			}
 		},
 		"sound" :

+ 1 - 1
config/defaultMods.json

@@ -19,7 +19,7 @@
 		"WEEKLY_GROWTH_PERCENT" : 10,
 		"NEUTRAL_STACK_EXP_DAILY" : 500,
 		"MAX_BUILDING_PER_TURN" : 1,
-		"DWELLINGS_ACCUMULATE_CREATURES" : true,
+		"DWELLINGS_ACCUMULATE_CREATURES" : false,
 		"ALL_CREATURES_GET_DOUBLE_MONTHS" : false,
 		"NEGATIVE_LUCK" : false,
 		"MAX_HEROES_AVAILABLE_PER_PLAYER" : 16,

+ 1 - 1
config/objects/moddables.json

@@ -27,7 +27,7 @@
 		"base" : {
 			"base" : {
 				"visitableFrom" : [ "+++", "+-+", "+++" ],
-				"mask" : [ "VV", "AV"]
+				"mask" : [ "VVV", "VAV"]
 			},
 			"sounds" : {
 				"removal" : ["KILLFADE"]

+ 32 - 1
config/schemas/creature.json

@@ -217,7 +217,7 @@
 				"missile": {
 					"type":"object",
 					"additionalProperties" : false,
-					"required" : [ "projectile", "frameAngles", "offset", "attackClimaxFrame" ],
+					"required" : [ "frameAngles", "offset", "attackClimaxFrame" ],
 					"description": "Missile description for archers",
 					"properties":{
 						"projectile": {
@@ -225,6 +225,37 @@
 							"description": "Path to projectile animation",
 							"format" : "defFile"
 						},
+						"ray": {
+							"type":"array",
+							"description": "Colors of ray projectile animation",
+							"minItems" : 1,
+							"items": {
+								"type":"object",
+								"required" : [ "start", "end" ],
+								"properties":{
+									"start":  {
+										"type":"array",
+										"minItems" : 4,
+										"maxItems" : 4,
+										"items": {
+											"minimum" : 0,
+											"maximum" : 255,
+											"type":"number"
+										}
+									 },
+									"end":  {
+										"type":"array",
+										"minItems" : 4,
+										"maxItems" : 4,
+										"items": {
+											"minimum" : 0,
+											"maximum" : 255,
+											"type":"number"
+										}
+									}
+								}
+							}
+						},
 						"frameAngles": {
 							"type":"array",
 							"description": "Angles of missile images, should go from 90 to -90",

+ 6 - 0
debian/changelog

@@ -1,3 +1,9 @@
+vcmi (1.0.0) jammy; urgency=medium
+
+  * New upstream release
+
+ -- Ivan Savenko <[email protected]>  Sun, 13 Nov 2022 17:28:31 +0200
+
 vcmi (0.99) trusty; urgency=medium
 
   * New upstream release

+ 1 - 1
debian/control

@@ -2,7 +2,7 @@ Source: vcmi
 Section: games
 Priority: optional
 Maintainer: Ivan Savenko <[email protected]>
-Build-Depends: debhelper (>= 8), cmake, libsdl2-dev, libsdl2-image-dev, libsdl2-ttf-dev, libsdl2-mixer-dev, zlib1g-dev, libavformat-dev, libswscale-dev, libboost-dev (>=1.48), libboost-program-options-dev (>=1.48), libboost-filesystem-dev (>=1.48), libboost-system-dev (>=1.48), libboost-locale-dev (>=1.48), libboost-thread-dev (>=1.48), qtbase5-dev
+Build-Depends: debhelper (>= 8), cmake, libsdl2-dev, libsdl2-image-dev, libsdl2-ttf-dev, libsdl2-mixer-dev, zlib1g-dev, libavformat-dev, libswscale-dev, libboost-dev (>=1.48), libboost-program-options-dev (>=1.48), libboost-filesystem-dev (>=1.48), libboost-system-dev (>=1.48), libboost-locale-dev (>=1.48), libboost-thread-dev (>=1.48),  qtbase5-dev, libtbb-dev, libfuzzylite-dev, libminizip-dev, libluajit-5.1-dev 
 Standards-Version: 3.9.1
 Homepage: http://vcmi.eu
 Vcs-Git: git://github.com/vcmi/vcmi.git

+ 0 - 2
launcher/modManager/cmodmanager.cpp

@@ -160,8 +160,6 @@ bool CModManager::canUninstallMod(QString modname)
 	if(!mod.isInstalled())
 		return addError(modname, "Mod is not installed");
 
-	if(mod.isEnabled())
-		return addError(modname, "Mod must be disabled first");
 	return true;
 }
 

+ 17 - 0
lib/CCreatureHandler.cpp

@@ -907,6 +907,23 @@ void CCreatureHandler::loadCreatureJson(CCreature * creature, const JsonNode & c
 
 	creature->animation.projectileImageName = config["graphics"]["missile"]["projectile"].String();
 
+	for(const JsonNode & value : config["graphics"]["missile"]["ray"].Vector())
+	{
+		CCreature::CreatureAnimation::RayColor color;
+
+		color.r1 = value["start"].Vector()[0].Integer();
+		color.g1 = value["start"].Vector()[1].Integer();
+		color.b1 = value["start"].Vector()[2].Integer();
+		color.a1 = value["start"].Vector()[3].Integer();
+
+		color.r2 = value["end"].Vector()[0].Integer();
+		color.g2 = value["end"].Vector()[1].Integer();
+		color.b2 = value["end"].Vector()[2].Integer();
+		color.a2 = value["end"].Vector()[3].Integer();
+
+		creature->animation.projectileRay.push_back(color);
+	}
+
 	creature->special = config["special"].Bool() || config["disabled"].Bool();
 
 	const JsonNode & sounds = config["sound"];

+ 12 - 0
lib/CCreatureHandler.h

@@ -63,6 +63,16 @@ public:
 
 	struct CreatureAnimation
 	{
+		struct RayColor {
+			uint8_t r1, g1, b1, a1;
+			uint8_t r2, g2, b2, a2;
+
+			template <typename Handler> void serialize(Handler &h, const int version)
+			{
+				h & r1 & g1 & b1 & a1 & r2 & g2 & b2 & a2;
+			}
+		};
+
 		double timeBetweenFidgets, idleAnimationTime,
 			   walkAnimationTime, attackAnimationTime, flightAnimationDistance;
 		int upperRightMissleOffsetX, rightMissleOffsetX, lowerRightMissleOffsetX,
@@ -72,6 +82,7 @@ public:
 		int troopCountLocationOffset, attackClimaxFrame;
 
 		std::string projectileImageName;
+		std::vector<RayColor> projectileRay;
 		//bool projectileSpin; //if true, appropriate projectile is spinning during flight
 
 		template <typename Handler> void serialize(Handler &h, const int version)
@@ -91,6 +102,7 @@ public:
 			h & troopCountLocationOffset;
 			h & attackClimaxFrame;
 			h & projectileImageName;
+			h & projectileRay;
 		}
 	} animation;
 

+ 2 - 0
lib/CGameState.cpp

@@ -2857,6 +2857,8 @@ void CGameState::replaceHeroesPlaceholders(const std::vector<CGameState::Campaig
 		heroToPlace->tempOwner = heroPlaceholder->tempOwner;
 		heroToPlace->pos = heroPlaceholder->pos;
 		heroToPlace->type = VLC->heroh->objects[heroToPlace->subID];
+		heroToPlace->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO,
+															   heroToPlace->type->heroClass->getIndex())->getTemplates().front();
 
 		for(auto &&i : heroToPlace->stacks)
 			i.second->type = VLC->creh->objects[i.second->getCreatureID()];

+ 3 - 6
lib/VCMIDirs.cpp

@@ -170,6 +170,9 @@ class VCMIDirsWIN32 final : public IVCMIDirs
 
 void VCMIDirsWIN32::init()
 {
+	std::locale::global(boost::locale::generator().generate("en_US.UTF-8"));
+	boost::filesystem::path::imbue(std::locale());
+
 	// Call base (init dirs)
 	IVCMIDirs::init();
 
@@ -698,11 +701,6 @@ namespace VCMIDirs
 		static bool initialized = false;
 		if (!initialized)
 		{
-			#ifdef VCMI_WINDOWS
-			std::locale::global(boost::locale::generator().generate("en_US.UTF-8"));
-			#endif
-			boost::filesystem::path::imbue(std::locale());
-
 			singleton.init();
 			initialized = true;
 		}
@@ -710,5 +708,4 @@ namespace VCMIDirs
 	}
 }
 
-
 VCMI_LIB_NAMESPACE_END

+ 1 - 1
lib/rmg/CMapGenOptions.cpp

@@ -70,7 +70,7 @@ void CMapGenOptions::setPlayerCount(si8 value)
 	assert((value >= 1 && value <= PlayerColor::PLAYER_LIMIT_I) || value == RANDOM_SIZE);
 	playerCount = value;
 
-	auto possibleCompPlayersCount = value;
+	auto possibleCompPlayersCount = PlayerColor::PLAYER_LIMIT_I - value;
 	if (compOnlyPlayerCount > possibleCompPlayersCount)
 		setCompOnlyPlayerCount(possibleCompPlayersCount);
 

+ 3 - 3
mapeditor/Animation.cpp

@@ -450,10 +450,10 @@ void ImageLoader::init(QPoint SpriteSize, QPoint Margins, QPoint FullSize)
 	margins = Margins;
 	fullSize = FullSize;
 	
-	memset((void *)image->bits(), 0, fullSize.y() * fullSize.x());
+	memset((void *)image->bits(), 0, fullSize.y() * image->bytesPerLine());
 	
 	lineStart = image->bits();
-	lineStart += margins.y() * fullSize.x() + margins.x();
+	lineStart += margins.y() * image->bytesPerLine() + margins.x();
 	position = lineStart;
 }
 
@@ -477,7 +477,7 @@ inline void ImageLoader::Load(size_t size, ui8 color)
 
 inline void ImageLoader::EndLine()
 {
-	lineStart += fullSize.x();
+	lineStart += image->bytesPerLine();
 	position = lineStart;
 }
 

+ 3 - 3
mapeditor/BitmapHandler.cpp

@@ -63,7 +63,7 @@ namespace BitmapHandler
 		{
 			it = 0xC;
 			//auto bitmap = QBitmap::fromData(qsize, pcx + it);
-			QImage image(pcx + it, width, height, QImage::Format_Indexed8);
+			QImage image(pcx + it, width, height, width, QImage::Format_Indexed8);
 			
 			//palette - last 256*3 bytes
 			QVector<QRgb> colorTable;
@@ -81,7 +81,7 @@ namespace BitmapHandler
 		}
 		else
 		{
-			QImage image(pcx + it, width, height, QImage::Format_RGB32);
+			QImage image(pcx + it, width, height, width * 3, QImage::Format_RGB888);
 			return image;
 		}
 	}
@@ -117,7 +117,7 @@ namespace BitmapHandler
 			{
 				logGlobal->error("Failed to open %s as H3 PCX!", fname);
 			}
-			return image;
+			return image.copy(); //copy must be returned here because buffer readFile.first used to build QImage will be cleaned after this line
 		}
 		else
 		{ //loading via QImage