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

Merge remote-tracking branch 'remotes/origin/develop' into feature/VCMIMapFormat1

Conflicts:
	lib/CArtHandler.h
	lib/mapObjects/MiscObjects.cpp
AlexVinS 9 жил өмнө
parent
commit
89d986fc6a

+ 19 - 11
AI/VCAI/Goals.cpp

@@ -154,8 +154,8 @@ bool Goals::AbstractGoal::operator== (AbstractGoal &g)
 
 //TODO: find out why the following are not generated automatically on MVS?
 
-namespace Goals 
-{ 
+namespace Goals
+{
 	template <>
 	void CGoal<Win>::accept (VCAI * ai)
 	{
@@ -367,7 +367,7 @@ TSubgoal FindObj::whatToDoToAchieve()
 
 std::string GetObj::completeMessage() const
 {
-	return "hero " + hero.get()->name + " captured Object ID = " + boost::lexical_cast<std::string>(objid); 
+	return "hero " + hero.get()->name + " captured Object ID = " + boost::lexical_cast<std::string>(objid);
 }
 
 TSubgoal GetObj::whatToDoToAchieve()
@@ -409,7 +409,7 @@ bool GetObj::fulfillsMe (TSubgoal goal)
 
 std::string VisitHero::completeMessage() const
 {
-	return "hero " + hero.get()->name + " visited hero " + boost::lexical_cast<std::string>(objid); 
+	return "hero " + hero.get()->name + " visited hero " + boost::lexical_cast<std::string>(objid);
 }
 
 TSubgoal VisitHero::whatToDoToAchieve()
@@ -435,10 +435,18 @@ TSubgoal VisitHero::whatToDoToAchieve()
 
 bool VisitHero::fulfillsMe (TSubgoal goal)
 {
-	if (goal->goalType == Goals::VISIT_TILE && cb->getObj(ObjectInstanceID(objid))->visitablePos() == goal->tile)
-		return true;
-	else
+	if (goal->goalType != Goals::VISIT_TILE)
+	{
 		return false;
+	}
+	auto obj = cb->getObj(ObjectInstanceID(objid));
+	if (!obj)
+	{
+		logAi->errorStream() << boost::format("Hero %s: VisitHero::fulfillsMe at %s: object %d not found")
+			% hero.name % goal->tile % objid;
+		return false;
+	}
+	return obj->visitablePos() == goal->tile;
 }
 
 TSubgoal GetArtOfType::whatToDoToAchieve()
@@ -458,7 +466,7 @@ TSubgoal ClearWayTo::whatToDoToAchieve()
 		return sptr (Goals::Explore());
 	}
 
-	return (fh->chooseSolution(getAllPossibleSubgoals()));	
+	return (fh->chooseSolution(getAllPossibleSubgoals()));
 }
 
 TGoalVec ClearWayTo::getAllPossibleSubgoals()
@@ -862,7 +870,7 @@ TSubgoal GatherTroops::whatToDoToAchieve()
 		{
 			auto creatures = vstd::tryAt(t->town->creatures, creature->level - 1);
 			if(!creatures)
-				continue; 
+				continue;
 
 			int upgradeNumber = vstd::find_pos(*creatures, creature->idNumber);
 			if(upgradeNumber < 0)
@@ -957,7 +965,7 @@ TGoalVec Conquer::getAllPossibleSubgoals()
 	std::vector<const CGObjectInstance *> objs;
 	for (auto obj : ai->visitableObjs)
 	{
-		if (conquerable(obj)) 
+		if (conquerable(obj))
 			objs.push_back (obj);
 	}
 
@@ -1035,7 +1043,7 @@ TGoalVec GatherArmy::getAllPossibleSubgoals()
 {
 	//get all possible towns, heroes and dwellings we may use
 	TGoalVec ret;
-	
+
 	//TODO: include evaluation of monsters gather in calculation
 	for (auto t : cb->getTownsInfo())
 	{

+ 5 - 4
AI/VCAI/VCAI.cpp

@@ -1987,7 +1987,6 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
 			if(teleportChannelProbingList.size())
 				doChannelProbing();
 		}
-		ret = !i;
 	}
 	if (h)
 	{
@@ -2002,6 +2001,8 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
 		completeGoal (sptr(Goals::VisitTile(dst).sethero(h))); //we stepped on some tile, anyway
 		completeGoal (sptr(Goals::ClearWayTo(dst).sethero(h)));
 
+		ret = (dst == h->visitablePos());
+
 		if(!ret) //reserve object we are heading towards
 		{
 			auto obj = vstd::frontOrNull(cb->getVisitableObjs(dst));
@@ -2020,7 +2021,7 @@ bool VCAI::moveHeroToTile(int3 dst, HeroPtr h)
 }
 void VCAI::tryRealize(Goals::Explore & g)
 {
-	throw cannotFulfillGoalException("EXPLORE is not a elementar goal!");
+	throw cannotFulfillGoalException("EXPLORE is not an elementar goal!");
 }
 
 void VCAI::tryRealize(Goals::RecruitHero & g)
@@ -2853,7 +2854,7 @@ BattleState AIStatus::getBattle()
 }
 
 void AIStatus::addQuery(QueryID ID, std::string description)
-{	
+{
 	if(ID == QueryID(-1))
 	{
         logAi->debugStream() << boost::format("The \"query\" has an id %d, it'll be ignored as non-query. Description: %s") % ID % description;
@@ -2878,7 +2879,7 @@ void AIStatus::removeQuery(QueryID ID)
 
 	std::string description = remainingQueries[ID];
 	remainingQueries.erase(ID);
-	
+
 	cv.notify_all();
     logAi->debugStream() << boost::format("Removing query %d - %s. Total queries count: %d") % ID % description % remainingQueries.size();
 }

+ 4 - 0
ChangeLog

@@ -8,6 +8,8 @@ GENERAL:
 - Angel Wings
 - Boots of Levitation
 * Implemented rumors in tavern window
+* New cheat code:
+- vcmiglaurung - gives 5000 crystal dragons into each slot
 
 ADVETURE AI:
 * Fixed AI trying to go through underground rock
@@ -19,6 +21,8 @@ RANDOM MAP GENERATOR:
 * Changed fractalization algorithm so it can create cycles
 * Zones will not have straight paths anymore, they are totally random
 * Added Thieves Guild random object (1 per zone)
+* Added Seer Huts with quests that match OH3
+* RMG will guarantee at least 100 pairs of Monoliths are available even if there are not enough different defs
 
 0.97 -> 0.98
 GENERAL:

+ 68 - 63
client/CMT.cpp

@@ -79,7 +79,7 @@ extern boost::thread_specific_ptr<bool> inGuiThread;
 SDL_Surface *screen = nullptr, //main screen surface
 	*screen2 = nullptr, //and hlp surface (used to store not-active interfaces layer)
 	*screenBuf = screen; //points to screen (if only advmapint is present) or screen2 (else) - should be used when updating controls which are not regularly redrawed
-	
+
 std::queue<SDL_Event> events;
 boost::mutex eventsM;
 
@@ -187,7 +187,7 @@ static void SDLLogCallback(void*           userdata,
 {
 	//todo: convert SDL log priority to vcmi log priority
 	//todo: make separate log domain for SDL
-	
+
 	logGlobal->debugStream() << "SDL(category " << category << "; priority " <<priority <<") "<<message;
 }
 
@@ -363,21 +363,21 @@ int main(int argc, char** argv)
 		}
 		GH.mainFPSmng->init(); //(!)init here AFTER SDL_Init() while using SDL for FPS management
 		atexit(SDL_Quit);
-		
+
 		SDL_LogSetOutputFunction(&SDLLogCallback, nullptr);
-		
+
 		int driversCount = SDL_GetNumRenderDrivers();
 		std::string preferredDriverName = video["driver"].String();
-		
+
 		logGlobal->infoStream() << "Found " << driversCount << " render drivers";
-		
+
 		for(int it = 0; it < driversCount; it++)
 		{
 			SDL_RendererInfo info;
 			SDL_GetRenderDriverInfo(it,&info);
-			
+
 			std::string driverName(info.name);
-			
+
 			if(!preferredDriverName.empty() && driverName == preferredDriverName)
 			{
 				preferredDriverIndex = it;
@@ -385,8 +385,8 @@ int main(int argc, char** argv)
 			}
 			else
 				logGlobal->infoStream() << "\t" << driverName;
-		}			
-		
+		}
+
 		config::CConfigHandler::GuiOptionsMap::key_type resPair(res["width"].Float(), res["height"].Float());
 		if (conf.guiOptions.count(resPair) == 0)
 		{
@@ -440,10 +440,15 @@ int main(int argc, char** argv)
 	CCS->musich->setVolume(settings["general"]["music"].Float());
     logGlobal->infoStream()<<"Initializing screen and sound handling: "<<pomtime.getDiff();
 
+#ifdef __APPLE__
+	// Ctrl+click should be treated as a right click on Mac OS X
+	SDL_SetHint(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, "1");
+#endif
+
 #ifndef VCMI_NO_THREADED_LOAD
 	//we can properly play intro only in the main thread, so we have to move loading to the separate thread
 	boost::thread loading(init);
-#else	 
+#else
 	init();
 #endif
 
@@ -632,7 +637,7 @@ void processCommand(const std::string &message)
 		for (auto & filename : list)
 		{
 			const bfs::path filePath = outPath / (filename.getName() + ".TXT");
-			
+
 			bfs::create_directories(filePath.parent_path());
 
 			bfs::ofstream file(filePath);
@@ -845,25 +850,25 @@ static bool checkVideoMode(int monitorIndex, int w, int h, int& bpp, bool fullsc
 			return true;
 		}
 	}
-	return false;	
+	return false;
 }
 
 static bool recreateWindow(int w, int h, int bpp, bool fullscreen)
 {
-	// VCMI will only work with 2 or 4 bytes per pixel	
+	// VCMI will only work with 2 or 4 bytes per pixel
 	vstd::amax(bpp, 16);
 	vstd::amin(bpp, 32);
 	if(bpp>16)
 		bpp = 32;
-	
+
 	int suggestedBpp = bpp;
 
 	if(!checkVideoMode(0,w,h,suggestedBpp,fullscreen))
 	{
 		logGlobal->errorStream() << "Error: SDL says that " << w << "x" << h << " resolution is not available!";
 		return false;
-	}	
-	
+	}
+
 	bool bufOnScreen = (screenBuf == screen);
 
 	screenBuf = nullptr; //it`s a link - just nullify
@@ -873,34 +878,34 @@ static bool recreateWindow(int w, int h, int bpp, bool fullscreen)
 		SDL_FreeSurface(screen2);
 		screen2 = nullptr;
 	}
-		
-		
+
+
 	if(nullptr != screen)
 	{
 		SDL_FreeSurface(screen);
 		screen = nullptr;
-	}	
-		
-	
+	}
+
+
 	if(nullptr != screenTexture)
 	{
 		SDL_DestroyTexture(screenTexture);
 		screenTexture = nullptr;
 	}
-	
-	if(nullptr != mainRenderer)	
+
+	if(nullptr != mainRenderer)
 	{
 		SDL_DestroyRenderer(mainRenderer);
 		mainRenderer = nullptr;
 	}
-		
+
 	if(nullptr != mainWindow)
 	{
 		SDL_DestroyWindow(mainWindow);
 		mainWindow = nullptr;
-	}	
-	
-	
+	}
+
+
 	if(fullscreen)
 	{
 		//in full-screen mode always use desktop resolution
@@ -911,33 +916,33 @@ static bool recreateWindow(int w, int h, int bpp, bool fullscreen)
 	{
 		mainWindow = SDL_CreateWindow(NAME.c_str(), SDL_WINDOWPOS_CENTERED,SDL_WINDOWPOS_CENTERED, w, h, 0);
 	}
-	
-	
-	
+
+
+
 	if(nullptr == mainWindow)
 	{
 		throw std::runtime_error("Unable to create window\n");
 	}
-	
-	
+
+
 	//create first available renderer if preferred not set. Use no flags, so HW accelerated will be preferred but SW renderer also will possible
 	mainRenderer = SDL_CreateRenderer(mainWindow,preferredDriverIndex,0);
 
 	if(nullptr == mainRenderer)
 	{
 		throw std::runtime_error("Unable to create renderer\n");
-	}	
-	
+	}
+
 	SDL_RendererInfo info;
 	SDL_GetRendererInfo(mainRenderer,&info);
-	logGlobal->infoStream() << "Created renderer " << info.name;	
-	
+	logGlobal->infoStream() << "Created renderer " << info.name;
+
 	SDL_RenderSetLogicalSize(mainRenderer, w, h);
-	
+
 	SDL_RenderSetViewport(mainRenderer, nullptr);
 
 
-	
+
 	#if (SDL_BYTEORDER == SDL_BIG_ENDIAN)
 		int bmask = 0xff000000;
 		int gmask = 0x00ff0000;
@@ -955,13 +960,13 @@ static bool recreateWindow(int w, int h, int bpp, bool fullscreen)
 	{
 		logGlobal->errorStream() << "Unable to create surface";
 		logGlobal->errorStream() << w << " "<<  h << " "<< bpp;
-		
+
 		logGlobal->errorStream() << SDL_GetError();
 		throw std::runtime_error("Unable to create surface");
-	}	
+	}
 	//No blending for screen itself. Required for proper cursor rendering.
 	SDL_SetSurfaceBlendMode(screen, SDL_BLENDMODE_NONE);
-	
+
 	screenTexture = SDL_CreateTexture(mainRenderer,
                                             SDL_PIXELFORMAT_ARGB8888,
                                             SDL_TEXTUREACCESS_STREAMING,
@@ -972,23 +977,23 @@ static bool recreateWindow(int w, int h, int bpp, bool fullscreen)
 		logGlobal->errorStream() << "Unable to create screen texture";
 		logGlobal->errorStream() << SDL_GetError();
 		throw std::runtime_error("Unable to create screen texture");
-	}	
-		
+	}
+
 	screen2 = CSDL_Ext::copySurface(screen);
 
 
 	if(nullptr == screen2)
 	{
 		throw std::runtime_error("Unable to copy surface\n");
-	}			
-	
+	}
+
 	screenBuf = bufOnScreen ? screen : screen2;
 
 	SDL_SetRenderDrawColor(mainRenderer, 0, 0, 0, 0);
 	SDL_RenderClear(mainRenderer);
 	SDL_RenderPresent(mainRenderer);
-		
-	return true;	
+
+	return true;
 }
 
 //used only once during initialization
@@ -997,7 +1002,7 @@ static void setScreenRes(int w, int h, int bpp, bool fullscreen, bool resetVideo
 	if(!recreateWindow(w,h,bpp,fullscreen))
 	{
 		throw std::runtime_error("Requested screen resolution is not available\n");
-	}	
+	}
 }
 
 static void fullScreenChanged()
@@ -1008,16 +1013,16 @@ static void fullScreenChanged()
 	const bool toFullscreen = full->Bool();
 
 	auto bitsPerPixel = screen->format->BitsPerPixel;
-	
+
 	auto w = screen->w;
 	auto h = screen->h;
-	
+
 	if(!recreateWindow(w,h,bitsPerPixel,toFullscreen))
 	{
 		//will return false and report error if video mode is not supported
-		return;	
-	}	
-	
+		return;
+	}
+
 	GH.totalRedraw();
 }
 
@@ -1025,7 +1030,7 @@ static void handleEvent(SDL_Event & ev)
 {
 	if((ev.type==SDL_QUIT) ||(ev.type == SDL_KEYDOWN && ev.key.keysym.sym==SDLK_F4 && (ev.key.keysym.mod & KMOD_ALT)))
 	{
-		handleQuit();	
+		handleQuit();
 		return;
 	}
 	else if(ev.type == SDL_KEYDOWN && ev.key.keysym.sym==SDLK_F4)
@@ -1040,8 +1045,8 @@ static void handleEvent(SDL_Event & ev)
 		{
 		case FORCE_QUIT:
 			{
-				handleQuit(false);	
-				return;			
+				handleQuit(false);
+				return;
 			}
 		    break;
 		case RETURN_TO_MAIN_MENU:
@@ -1080,8 +1085,8 @@ static void handleEvent(SDL_Event & ev)
 			fullScreenChanged();
 			break;
 		default:
-			logGlobal->errorStream() << "Unknown user event. Code " << ev.user.code;		
-			break;	
+			logGlobal->errorStream() << "Unknown user event. Code " << ev.user.code;
+			break;
 		}
 
 		return;
@@ -1098,8 +1103,8 @@ static void handleEvent(SDL_Event & ev)
 	{
 		boost::unique_lock<boost::mutex> lock(eventsM);
 		events.push(ev);
-	}	
-	
+	}
+
 }
 
 
@@ -1114,12 +1119,12 @@ static void mainLoop()
 	while(1) //main SDL events loop
 	{
 		SDL_Event ev;
-		
+
 		while(1 == SDL_PollEvent(&ev))
 		{
 			handleEvent(ev);
 		}
-		
+
 		GH.renderFrame();
 
 	}

+ 42 - 14
client/CPlayerInterface.cpp

@@ -119,7 +119,7 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player)
 	isAutoFightOn = false;
 
 	duringMovement = false;
-	ignoreEvents = false;	
+	ignoreEvents = false;
 }
 
 CPlayerInterface::~CPlayerInterface()
@@ -647,6 +647,8 @@ void CPlayerInterface::battleStart(const CCreatureSet *army1, const CCreatureSet
 		autofightingAI->battleStart(army1, army2, int3(0,0,0), hero1, hero2, side);
 		isAutoFightOn = true;
 		cb->registerBattleInterface(autofightingAI);
+		// Player shouldn't be able to move on adventure map if quick combat is going
+		adventureInt->quickCombatLock();
 	}
 
 	//Don't wait for dialogs when we are non-active hot-seat player
@@ -822,16 +824,16 @@ BattleAction CPlayerInterface::activeStack(const CStack * stack) //called when i
 	//tidy up
 	BattleAction ret = *(b->givenCommand->data);
 	vstd::clear_pointer(b->givenCommand->data);
-	
+
 	if(ret.actionType == Battle::CANCEL)
 	{
 		if(stackId != ret.stackNumber)
 			logGlobal->error("Not current active stack action canceled");
-		logGlobal->traceStream() << "Canceled command for " << stackName;			
+		logGlobal->traceStream() << "Canceled command for " << stackName;
 	}
 	else
 		logGlobal->traceStream() << "Giving command for " << stackName;
-		
+
 	return ret;
 }
 
@@ -843,6 +845,7 @@ void CPlayerInterface::battleEnd(const BattleResult *br)
 		isAutoFightOn = false;
 		cb->unregisterBattleInterface(autofightingAI);
 		autofightingAI.reset();
+		adventureInt->quickCombatUnlock();
 
 		if(!battleInt)
 		{
@@ -1332,7 +1335,7 @@ void CPlayerInterface::moveHero( const CGHeroInstance *h, CGPath path )
 	if(showingDialog->get() || !dialogs.empty())
 		return;
 
-	duringMovement = true;
+	setMovementStatus(true);
 
 	if (adventureInt && adventureInt->isHeroSleeping(h))
 	{
@@ -1344,8 +1347,6 @@ void CPlayerInterface::moveHero( const CGHeroInstance *h, CGPath path )
 	}
 
 	boost::thread moveHeroTask(std::bind(&CPlayerInterface::doMoveHero,this,h,path));
-
-
 }
 
 bool CPlayerInterface::shiftPressed() const
@@ -1556,6 +1557,7 @@ void CPlayerInterface::centerView (int3 pos, int focusTime)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	waitWhileDialog();
+	CCS->curh->hide();
 	adventureInt->centerOn (pos);
 	if(focusTime)
 	{
@@ -1566,6 +1568,7 @@ void CPlayerInterface::centerView (int3 pos, int focusTime)
 			SDL_Delay(focusTime);
 		}
 	}
+	CCS->curh->show();
 }
 
 void CPlayerInterface::objectRemoved( const CGObjectInstance *obj )
@@ -1607,11 +1610,11 @@ void CPlayerInterface::update()
 {
 	// Make sure that gamestate won't change when GUI objects may obtain its parts on event processing or drawing request
 	boost::shared_lock<boost::shared_mutex> gsLock(cb->getGsMutex());
-	
-	// While mutexes were locked away we may be have stopped being the active interface	
+
+	// While mutexes were locked away we may be have stopped being the active interface
 	if(LOCPLINT != this)
 		return;
-	
+
 	//if there are any waiting dialogs, show them
 	if((howManyPeople <= 1 || makingTurn) && !dialogs.empty() && !showingDialog->get())
 	{
@@ -2192,7 +2195,7 @@ void CPlayerInterface::advmapSpellCast(const CGHeroInstance * caster, int spellI
 		int level = caster->getSpellSchoolLevel(spell);
 		adventureInt->worldViewOptions.showAllTerrain = (level>2);
 	}
-	
+
 	auto castSoundPath = spell->getCastSound();
 	if (!castSoundPath.empty())
 		CCS->soundh->playSound(castSoundPath);
@@ -2507,9 +2510,20 @@ void CPlayerInterface::stacksRebalanced(const StackLocation &src, const StackLoc
 	garrisonsChanged(objects);
 }
 
+void CPlayerInterface::askToAssembleArtifact(const ArtifactLocation &al)
+{
+	auto hero = dynamic_cast<const CGHeroInstance*>(al.relatedObj());
+	if(hero)
+	{
+		CArtPlace::askToAssemble(hero->getArt(al.slot), al.slot, hero);
+	}
+}
+
 void CPlayerInterface::artifactPut(const ArtifactLocation &al)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
+	adventureInt->infoBar.showSelection();
+	askToAssembleArtifact(al);
 }
 
 void CPlayerInterface::artifactRemoved(const ArtifactLocation &al)
@@ -2534,6 +2548,7 @@ void CPlayerInterface::artifactMoved(const ArtifactLocation &src, const Artifact
 		if(artWin)
 			artWin->artifactMoved(src, dst);
 	}
+	askToAssembleArtifact(dst);
 }
 
 void CPlayerInterface::artifactAssembled(const ArtifactLocation &al)
@@ -2629,6 +2644,19 @@ bool CPlayerInterface::capturedAllEvents()
 	return false;
 }
 
+void CPlayerInterface::setMovementStatus(bool value)
+{
+	duringMovement = value;
+	if(value)
+	{
+		CCS->curh->hide();
+	}
+	else
+	{
+		CCS->curh->show();
+	}
+}
+
 void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
 {
 	int i = 1;
@@ -2773,15 +2801,15 @@ void CPlayerInterface::doMoveHero(const CGHeroInstance * h, CGPath path)
 		adventureInt->updateNextHero(h);
 	}
 
-	duringMovement = false;
+	setMovementStatus(false);
 }
 
 void CPlayerInterface::showWorldViewEx(const std::vector<ObjectPosInfo>& objectPositions)
 {
 	EVENT_HANDLER_CALLED_BY_CLIENT;
 	//TODO: showWorldViewEx
-	
+
 	std::copy(objectPositions.begin(), objectPositions.end(), std::back_inserter(adventureInt->worldViewOptions.iconPositions));
-	
+
 	viewWorldMap();
 }

+ 3 - 1
client/CPlayerInterface.h

@@ -197,7 +197,7 @@ public:
 	void showComp(const Component &comp, std::string message) override; //display component in the advmapint infobox
 	void saveGame(COSer & h, const int version) override; //saving
 	void loadGame(CISer & h, const int version) override; //loading
-	void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions) override;	
+	void showWorldViewEx(const std::vector<ObjectPosInfo> & objectPositions) override;
 
 	//for battles
 	void actionFinished(const BattleAction& action) override;//occurs AFTER action taken by active stack or by the hero
@@ -295,6 +295,8 @@ private:
 	bool ignoreEvents;
 
 	void doMoveHero(const CGHeroInstance *h, CGPath path);
+	void setMovementStatus(bool value);
+	void askToAssembleArtifact(const ArtifactLocation &al);
 };
 
 extern CPlayerInterface * LOCPLINT;

+ 33 - 19
client/widgets/CArtifactHolder.cpp

@@ -214,6 +214,32 @@ void CArtPlace::clickLeft(tribool down, bool previousState)
 	}
 }
 
+bool CArtPlace::askToAssemble(const CArtifactInstance *art, ArtifactPosition slot,
+                              const CGHeroInstance *hero)
+{
+	std::vector<const CArtifact *> assemblyPossibilities = art->assemblyPossibilities(hero);
+
+	// If the artifact can be assembled, display dialog.
+	for(const CArtifact *combination : assemblyPossibilities)
+	{
+		LOCPLINT->showArtifactAssemblyDialog(
+			art->artType->id,
+			combination->id,
+			true,
+			std::bind(&CCallback::assembleArtifacts, LOCPLINT->cb.get(), hero, slot, true, combination->id),
+			0);
+
+		if(assemblyPossibilities.size() > 2)
+		{
+			logGlobal->warnStream() << boost::format(
+				"More than one possibility of assembling on %s... taking only first")
+				% art->artType->Name();
+		}
+		return true;
+	}
+	return false;
+}
+
 void CArtPlace::clickRight(tribool down, bool previousState)
 {
 	if(down && ourArt && !locked && text.size() && !picked)  //if there is no description or it's a lock, do nothing ;]
@@ -225,20 +251,8 @@ void CArtPlace::clickRight(tribool down, bool previousState)
 				std::vector<const CArtifact *> assemblyPossibilities = ourArt->assemblyPossibilities(ourOwner->curHero);
 
 				// If the artifact can be assembled, display dialog.
-				for(const CArtifact *combination : assemblyPossibilities)
+				if (askToAssemble(ourArt, slotID, ourOwner->curHero))
 				{
-					LOCPLINT->showArtifactAssemblyDialog(
-						ourArt->artType->id,
-						combination->id,
-						true,
-						std::bind(&CCallback::assembleArtifacts, LOCPLINT->cb.get(), ourOwner->curHero, slotID, true, combination->id),
-						0);
-
-					if(assemblyPossibilities.size() > 2)
-					{
-                        logGlobal->warnStream() << "More than one possibility of assembling... taking only first";
-						break;
-					}
 					return;
 				}
 
@@ -303,10 +317,10 @@ void CArtPlace::deselect ()
 		for(int i = 0; i < GameConstants::BACKPACK_START; i++)
 		{
 			auto place = ourOwner->getArtPlace(i);
-			
+
 			if(nullptr != place)//getArtPlace may return null
 				place->pickSlot(false);
-		}			
+		}
 	}
 
 	CCS->curh->dragAndDropCursor(nullptr);
@@ -454,12 +468,12 @@ void CArtifactsOfHero::setHero(const CGHeroInstance * hero)
 		backpackPos = 0;
 
 	// Fill the slots for worn artifacts and backpack.
-	
+
 	for(auto p : artWorn)
 	{
 		setSlotData(p.second, p.first);
 	}
-	
+
 	scrollBackpack(0);
 }
 
@@ -587,8 +601,8 @@ void CArtifactsOfHero::setSlotData(CArtPlace* artPlace, ArtifactPosition slotID)
 
 	if(const ArtSlotInfo *asi = curHero->getSlot(slotID))
 	{
-		artPlace->setArtifact(asi->artifact);
 		artPlace->lockSlot(asi->locked);
+		artPlace->setArtifact(asi->artifact);
 	}
 	else
 		artPlace->setArtifact(nullptr);
@@ -853,7 +867,7 @@ CArtPlace * CArtifactsOfHero::getArtPlace(int slot)
 		for(CArtPlace *ap : backpack)
 			if(ap->slotID == slot)
 				return ap;
-		return nullptr;				
+		return nullptr;
 	}
 }
 

+ 4 - 2
client/widgets/CArtifactHolder.h

@@ -74,15 +74,17 @@ public:
 
 	void setMeAsDest(bool backpackAsVoid = true);
 	void setArtifact(const CArtifactInstance *art);
+	static bool askToAssemble(const CArtifactInstance *art, ArtifactPosition slot,
+	                          const CGHeroInstance *hero);
 };
 
 /// Contains artifacts of hero. Distincts which artifacts are worn or backpacked
 class CArtifactsOfHero : public CIntObject
 {
 	const CGHeroInstance * curHero;
-	
+
 	std::map<ArtifactPosition, CArtPlace *> artWorn;
- 
+
 	std::vector<CArtPlace *> backpack; //hero's visible backpack (only 5 elements!)
 	int backpackPos; //number of first art visible in backpack (in hero's vector)
 

+ 14 - 2
client/widgets/CComponent.cpp

@@ -186,19 +186,31 @@ std::string CComponent::getSubtitleInternal()
 	case artifact:   return CGI->arth->artifacts[subtype]->Name();
 	case experience:
 		{
-			if (subtype == 1) //+1 level - tree of knowledge
+			if(subtype == 1) //+1 level - tree of knowledge
 			{
 				std::string level = CGI->generaltexth->allTexts[442];
 				boost::replace_first(level, "1", boost::lexical_cast<std::string>(val));
 				return level;
 			}
 			else
+			{
 				return boost::lexical_cast<std::string>(val); //amount of experience OR level required for seer hut;
+			}
 		}
 	case spell:      return CGI->spellh->objects[subtype]->name;
 	case morale:     return "";
 	case luck:       return "";
-	case building:   return CGI->townh->factions[subtype]->town->buildings[BuildingID(val)]->Name();
+	case building:
+		{
+			auto building = CGI->townh->factions[subtype]->town->buildings[BuildingID(val)];
+			if(!building)
+			{
+				logGlobal->errorStream() << boost::format("Town of faction %s has no building #%d")
+					% CGI->townh->factions[subtype]->town->faction->name % val;
+				return (boost::format("Missing building #%d") % val).str();
+			}
+			return building->Name();
+		}
 	case hero:       return "";
 	case flag:       return CGI->generaltexth->capColors[subtype];
 	}

+ 2 - 2
client/widgets/CGarrisonInt.cpp

@@ -73,11 +73,11 @@ void CGarrisonSlot::hover (bool on)
 			{
 				if(upg == EGarrisonType::UP)
 				{
-					temp = CGI->generaltexth->tcommands[32]; //Select %s (visiting)
+					temp = CGI->generaltexth->tcommands[12]; //Select %s (in garrison)
 				}
 				else if(owner->armedObjs[0] && (owner->armedObjs[0]->ID == Obj::TOWN || owner->armedObjs[0]->ID == Obj::HERO))
 				{
-					temp = CGI->generaltexth->tcommands[12]; //Select %s (in garrison)
+					temp = CGI->generaltexth->tcommands[32]; //Select %s (visiting)
 				}
 				else
 				{

+ 41 - 21
client/windows/CAdvmapInterface.cpp

@@ -322,18 +322,21 @@ void CTerrainRect::showAnim(SDL_Surface * to)
 		show(to); // currently the same; maybe we should pass some flag to map handler so it redraws ONLY tiles that need redraw instead of full
 }
 
-int3 CTerrainRect::whichTileIsIt(const int & x, const int & y)
+int3 CTerrainRect::whichTileIsIt(const int x, const int y)
 {
 	int3 ret;
-	ret.x = adventureInt->position.x + ((GH.current->motion.x-CGI->mh->offsetX-pos.x)/32);
-	ret.y = adventureInt->position.y + ((GH.current->motion.y-CGI->mh->offsetY-pos.y)/32);
+	ret.x = adventureInt->position.x + ((x-CGI->mh->offsetX-pos.x)/32);
+	ret.y = adventureInt->position.y + ((y-CGI->mh->offsetY-pos.y)/32);
 	ret.z = adventureInt->position.z;
 	return ret;
 }
 
 int3 CTerrainRect::whichTileIsIt()
 {
-	return whichTileIsIt(GH.current->motion.x,GH.current->motion.y);
+	if(GH.current)
+		return whichTileIsIt(GH.current->motion.x,GH.current->motion.y);
+	else
+		return int3(-1);
 }
 
 int3 CTerrainRect::tileCountOnScreen()
@@ -755,14 +758,15 @@ void CAdvMapInt::updateSleepWake(const CGHeroInstance *h)
 
 void CAdvMapInt::updateMoveHero(const CGHeroInstance *h, tribool hasPath)
 {
-	//default value is for everywhere but CPlayerInterface::moveHero, because paths are not updated from there immediately
-	if (hasPath == boost::indeterminate)
-		 hasPath = LOCPLINT->paths[h].nodes.size() ? true : false;
-	if (!h)
+	if(!h)
 	{
 		moveHero->block(true);
 		return;
 	}
+	//default value is for everywhere but CPlayerInterface::moveHero, because paths are not updated from there immediately
+	if(boost::logic::indeterminate(hasPath))
+		hasPath = LOCPLINT->paths[h].nodes.size() ? true : false;
+
 	moveHero->block(!hasPath || (h->movement == 0));
 }
 
@@ -1417,13 +1421,13 @@ void CAdvMapInt::tileLClicked(const int3 &mapPos)
 	bool canSelect = topBlocking && topBlocking->ID == Obj::HERO && topBlocking->tempOwner == LOCPLINT->playerID;
 	canSelect |= topBlocking && topBlocking->ID == Obj::TOWN && LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, topBlocking->tempOwner);
 
-	if (selection->ID != Obj::HERO) //hero is not selected (presumably town)
+	if(selection->ID != Obj::HERO) //hero is not selected (presumably town)
 	{
 		assert(!terrain.currentPath); //path can be active only when hero is selected
 		if(selection == topBlocking) //selected town clicked
 			LOCPLINT->openTownWindow(static_cast<const CGTownInstance*>(topBlocking));
-		else if ( canSelect )
-				select(static_cast<const CArmedInstance*>(topBlocking), false);
+		else if(canSelect)
+			select(static_cast<const CArmedInstance*>(topBlocking), false);
 		return;
 	}
 	else if(const CGHeroInstance * currentHero = curHero()) //hero is selected
@@ -1441,22 +1445,26 @@ void CAdvMapInt::tileLClicked(const int3 &mapPos)
 		}
 		else //still here? we need to move hero if we clicked end of already selected path or calculate a new path otherwise
 		{
-			if (terrain.currentPath  &&  terrain.currentPath->endPos() == mapPos)//we'll be moving
+			if(terrain.currentPath && terrain.currentPath->endPos() == mapPos)//we'll be moving
 			{
-				if (CGI->mh->canStartHeroMovement())
-					LOCPLINT->moveHero(currentHero,*terrain.currentPath);
+				if(CGI->mh->canStartHeroMovement())
+					LOCPLINT->moveHero(currentHero, *terrain.currentPath);
 				return;
 			}
-			else/* if(mp.z == currentHero->pos.z)*/ //remove old path and find a new one if we clicked on the map level on which hero is present
+			else //remove old path and find a new one if we clicked on accessible tile
 			{
 				CGPath &path = LOCPLINT->paths[currentHero];
-				terrain.currentPath = &path;
-				bool gotPath = LOCPLINT->cb->getPathsInfo(currentHero)->getPath(path, mapPos); //try getting path, erase if failed
-				updateMoveHero(currentHero);
-				if (!gotPath)
-					LOCPLINT->eraseCurrentPathOf(currentHero);
+				CGPath newpath;
+				bool gotPath = LOCPLINT->cb->getPathsInfo(currentHero)->getPath(newpath, mapPos); //try getting path, erase if failed
+				if(gotPath && newpath.nodes.size())
+					path = newpath;
+
+				if(path.nodes.size())
+					terrain.currentPath = &path;
 				else
-					return;
+					LOCPLINT->eraseCurrentPathOf(currentHero);
+
+				updateMoveHero(currentHero);
 			}
 		}
 	} //end of hero is selected "case"
@@ -1704,6 +1712,18 @@ void CAdvMapInt::adjustActiveness(bool aiTurnStart)
 		activate();
 }
 
+void CAdvMapInt::quickCombatLock()
+{
+	if(!duringAITurn)
+		deactivate();
+}
+
+void CAdvMapInt::quickCombatUnlock()
+{
+	if(!duringAITurn)
+		activate();
+}
+
 void CAdvMapInt::changeMode(EAdvMapMode newMode, float newScale /* = 0.4f */)
 {
 	if (mode != newMode)

+ 3 - 1
client/windows/CAdvmapInterface.h

@@ -74,7 +74,7 @@ public:
 	void showAll(SDL_Surface * to) override;
 	void showAnim(SDL_Surface * to);
 	void showPath(const SDL_Rect * extRect, SDL_Surface * to);
-	int3 whichTileIsIt(const int & x, const int & y); //x,y are cursor position
+	int3 whichTileIsIt(const int x, const int y); //x,y are cursor position
 	int3 whichTileIsIt(); //uses current cursor pos
 	/// @returns number of visible tiles on screen respecting current map scaling
 	int3 tileCountOnScreen();
@@ -224,6 +224,8 @@ public:
 	void aiTurnStarted();
 
 	void adjustActiveness(bool aiTurnStart); //should be called every time at AI/human turn transition; blocks GUI during AI turn
+	void quickCombatLock(); //should be called when quick battle started
+	void quickCombatUnlock();
 	void tileLClicked(const int3 &mapPos);
 	void tileHovered(const int3 &mapPos);
 	void tileRClicked(const int3 &mapPos);

+ 35 - 2
lib/CArtHandler.cpp

@@ -1178,9 +1178,42 @@ const CArtifactInstance * CArtifactSet::getArtByInstanceId( ArtifactInstanceID a
 	return nullptr;
 }
 
-bool CArtifactSet::hasArt(ui32 aid, bool onlyWorn /*= false*/) const
+bool CArtifactSet::hasArt(ui32 aid, bool onlyWorn /*= false*/,
+                          bool searchBackpackAssemblies /*= false*/) const
 {
-	return getArtPos(aid, onlyWorn) != ArtifactPosition::PRE_FIRST;
+	return getArtPos(aid, onlyWorn) != ArtifactPosition::PRE_FIRST ||
+	       (searchBackpackAssemblies && getHiddenArt(aid));
+}
+
+std::pair<const CCombinedArtifactInstance *, const CArtifactInstance *>
+CArtifactSet::searchForConstituent(int aid) const
+{
+	for(auto & slot : artifactsInBackpack)
+	{
+		auto art = slot.artifact;
+		if(art->canBeDisassembled())
+		{
+			auto ass = static_cast<CCombinedArtifactInstance *>(art.get());
+			for(auto& ci : ass->constituentsInfo)
+			{
+				if(ci.art->artType->id == aid)
+				{
+					return {ass, ci.art};
+				}
+			}
+		}
+	}
+	return {nullptr, nullptr};
+}
+
+const CArtifactInstance *CArtifactSet::getHiddenArt(int aid) const
+{
+	return searchForConstituent(aid).second;
+}
+
+const CCombinedArtifactInstance *CArtifactSet::getAssemblyByConstituent(int aid) const
+{
+	return searchForConstituent(aid).first;
 }
 
 const ArtSlotInfo * CArtifactSet::getSlot(ArtifactPosition pos) const

+ 17 - 8
lib/CArtHandler.h

@@ -133,7 +133,9 @@ public:
 	virtual bool canBeDisassembled() const;
 	virtual void putAt(ArtifactLocation al);
 	virtual void removeFrom(ArtifactLocation al);
-	virtual bool isPart(const CArtifactInstance *supposedPart) const; //checks if this a part of this artifact: artifact instance is a part of itself, additionally truth is returned for consituents of combined arts
+	/// Checks if this a part of this artifact: artifact instance is a part
+	/// of itself, additionally truth is returned for constituents of combined arts
+	virtual bool isPart(const CArtifactInstance *supposedPart) const;
 
 	std::vector<const CArtifact *> assemblyPossibilities(const CArtifactSet *h) const;
 	void move(ArtifactLocation src, ArtifactLocation dst);
@@ -177,7 +179,7 @@ public:
 
 	void createConstituents();
 	void addAsConstituent(CArtifactInstance *art, ArtifactPosition slot);
-	CArtifactInstance *figureMainConstituent(const ArtifactLocation al); //main constituent is replcaed with us (combined art), not lock
+	CArtifactInstance *figureMainConstituent(const ArtifactLocation al); //main constituent is replaced with us (combined art), not lock
 
 	CCombinedArtifactInstance();
 
@@ -270,10 +272,8 @@ struct DLL_LINKAGE ArtSlotInfo
 	ConstTransitivePtr<CArtifactInstance> artifact;
 	ui8 locked; //if locked, then artifact points to the combined artifact
 
-	ArtSlotInfo()
-	{
-		locked = false;
-	}
+	ArtSlotInfo() : locked(false) {}
+
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & artifact & locked;
@@ -293,10 +293,16 @@ public:
 	const ArtSlotInfo *getSlot(ArtifactPosition pos) const;
 	const CArtifactInstance* getArt(ArtifactPosition pos, bool excludeLocked = true) const; //nullptr - no artifact
 	CArtifactInstance* getArt(ArtifactPosition pos, bool excludeLocked = true); //nullptr - no artifact
-	ArtifactPosition getArtPos(int aid, bool onlyWorn = true) const; //looks for equipped artifact with given ID and returns its slot ID or -1 if none(if more than one such artifact lower ID is returned)
+	/// Looks for equipped artifact with given ID and returns its slot ID or -1 if none
+	/// (if more than one such artifact lower ID is returned)
+	ArtifactPosition getArtPos(int aid, bool onlyWorn = true) const;
 	ArtifactPosition getArtPos(const CArtifactInstance *art) const;
 	const CArtifactInstance *getArtByInstanceId(ArtifactInstanceID artInstId) const;
-	bool hasArt(ui32 aid, bool onlyWorn = false) const; //checks if hero possess artifact of given id (either in backack or worn)
+	/// Search for constituents of assemblies in backpack which do not have an ArtifactPosition
+	const CArtifactInstance *getHiddenArt(int aid) const;
+	const CCombinedArtifactInstance *getAssemblyByConstituent(int aid) const;
+	/// Checks if hero possess artifact of given id (either in backack or worn)
+	bool hasArt(ui32 aid, bool onlyWorn = false, bool searchBackpackAssemblies = false) const;
 	bool isPositionFree(ArtifactPosition pos, bool onlyLockCheck = false) const;
 	si32 getArtTypeId(ArtifactPosition pos) const;
 
@@ -313,4 +319,7 @@ public:
 protected:
 	void writeJson(JsonNode & json) const;
 	void readJson(const JsonNode & json);
+
+protected:
+	std::pair<const CCombinedArtifactInstance *, const CArtifactInstance *> searchForConstituent(int aid) const;
 };

+ 4 - 3
lib/CGameState.cpp

@@ -1658,15 +1658,16 @@ void CGameState::initStartingBonus()
 		switch(scenarioOps->playerInfos[elem.first].bonus)
 		{
 		case PlayerSettings::GOLD:
-			elem.second.resources[Res::GOLD] += rand.nextInt(500, 1000);
+			elem.second.resources[Res::GOLD] += rand.nextInt(5, 10) * 100;
 			break;
 		case PlayerSettings::RESOURCE:
 			{
 				int res = VLC->townh->factions[scenarioOps->playerInfos[elem.first].castle]->town->primaryRes;
 				if(res == Res::WOOD_AND_ORE)
 				{
-					elem.second.resources[Res::WOOD] += rand.nextInt(5, 10);
-					elem.second.resources[Res::ORE] += rand.nextInt(5, 10);
+					int amount = rand.nextInt(5, 10);
+					elem.second.resources[Res::WOOD] += amount;
+					elem.second.resources[Res::ORE] += amount;
 				}
 				else
 				{

+ 1 - 1
lib/GameConstants.h

@@ -14,7 +14,7 @@
 
 namespace GameConstants
 {
-	const std::string VCMI_VERSION = "VCMI 0.98f";
+	const std::string VCMI_VERSION = "VCMI 0.98g";
 
 	const int BFIELD_WIDTH = 17;
 	const int BFIELD_HEIGHT = 11;

+ 30 - 4
lib/NetPacksLib.cpp

@@ -340,7 +340,7 @@ DLL_LINKAGE void RemoveBonus::applyGs( CGameState *gs )
 		if(b->source == source && b->sid == id)
 		{
 			bonus = *b; //backup bonus (to show to interfaces later)
-			node->removeBonus(b);			
+			node->removeBonus(b);
 			break;
 		}
 	}
@@ -754,7 +754,7 @@ DLL_LINKAGE const CArtifactInstance *ArtifactLocation::getArt() const
 			return s->artifact;
 		else
 		{
-            logNetwork->warnStream() << "ArtifactLocation::getArt: That location is locked!";
+			logNetwork->warnStream() << "ArtifactLocation::getArt: This location is locked!";
 			return nullptr;
 		}
 	}
@@ -914,6 +914,32 @@ DLL_LINKAGE void PutArtifact::applyGs( CGameState *gs )
 
 DLL_LINKAGE void EraseArtifact::applyGs( CGameState *gs )
 {
+	auto slot = al.getSlot();
+	if(slot->locked)
+	{
+		logGlobal->debugStream() << "Erasing locked artifact: " << slot->artifact->artType->Name();
+		DisassembledArtifact dis;
+		dis.al.artHolder = al.artHolder;
+		auto aset = al.getHolderArtSet();
+		bool found = false;
+		for(auto& p : aset->artifactsWorn)
+		{
+			auto art = p.second.artifact;
+			if(art->canBeDisassembled() && art->isPart(slot->artifact))
+			{
+				dis.al.slot = aset->getArtPos(art);
+				found = true;
+				break;
+			}
+		}
+		assert(found && "Failed to determine the assembly this locked artifact belongs to");
+		logGlobal->debugStream() << "Found the corresponding assembly: " << dis.al.getSlot()->artifact->artType->Name();
+		dis.applyGs(gs);
+	}
+	else
+	{
+		logGlobal->debugStream() << "Erasing artifact " << slot->artifact->artType->Name();
+	}
 	al.removeArtifact();
 }
 
@@ -1262,7 +1288,7 @@ DLL_LINKAGE void BattleStackAttacked::applyGs( CGameState *gs )
 	{
 		//"hide" killed creatures instead so we keep info about it
 		at->state.insert(EBattleStackState::DEAD_CLONE);
-		
+
 		for(CStack * s : gs->curB->stacks)
 		{
 			if(s->cloneID == at->ID)
@@ -1375,7 +1401,7 @@ void actualizeEffect(CStack * s, const Bonus & ef)
 			stackBonus->turnsRemain = std::max(stackBonus->turnsRemain, ef.turnsRemain);
 		}
 	}
-	CBonusSystemNode::treeHasChanged();	
+	CBonusSystemNode::treeHasChanged();
 }
 
 void actualizeEffect(CStack * s, const std::vector<Bonus> & ef)

+ 10 - 0
lib/mapObjects/CObjectClassesHandler.cpp

@@ -315,6 +315,16 @@ void CObjectClassesHandler::afterLoadFinalization()
 				logGlobal->warnStream() << "No templates found for " << entry.first << ":" << obj.first;
 		}
 	}
+
+	//duplicate existing two-way portals to make reserve for RMG
+	auto& portalVec = objects[Obj::MONOLITH_TWO_WAY]->objects;
+	size_t portalCount = portalVec.size();
+	size_t currentIndex = portalCount;
+	while (portalVec.size() < 100)
+	{
+		portalVec[currentIndex] = portalVec[currentIndex % portalCount];
+		currentIndex++;
+	}
 }
 
 std::string CObjectClassesHandler::getObjectName(si32 type) const

+ 10 - 4
lib/mapObjects/CObjectHandler.cpp

@@ -183,7 +183,7 @@ std::set<int3> CGObjectInstance::getBlockedPos() const
 	{
 		for(int h=0; h<getHeight(); ++h)
 		{
-			if (appearance.isBlockedAt(w, h))
+			if(appearance.isBlockedAt(w, h))
 				ret.insert(int3(pos.x - w, pos.y - h, pos.z));
 		}
 	}
@@ -205,7 +205,13 @@ void CGObjectInstance::setType(si32 ID, si32 subID)
 	//recalculate blockvis tiles - new appearance might have different blockmap than before
 	cb->gameState()->map->removeBlockVisTiles(this, true);
 	auto handler = VLC->objtypeh->getHandlerFor(ID, subID);
-	if (!handler->getTemplates(tile.terType).empty())
+	if(!handler)
+	{
+		logGlobal->errorStream() << boost::format(
+			  "Unknown object type %d:%d at %s") % ID % subID % visitablePos();
+		return;
+	}
+	if(!handler->getTemplates(tile.terType).empty())
 		appearance = handler->getTemplates(tile.terType)[0];
 	else
 		appearance = handler->getTemplates()[0]; // get at least some appearance since alternative is crash
@@ -410,9 +416,9 @@ int3 IBoatGenerator::bestLocation() const
 
 	for (auto & offset : offsets)
 	{
-		if (const TerrainTile *tile = IObjectInterface::cb->getTile(o->pos + offset, false)) //tile is in the map
+		if(const TerrainTile *tile = IObjectInterface::cb->getTile(o->pos + offset, false)) //tile is in the map
 		{
-			if (tile->terType == ETerrainType::WATER  &&  (!tile->blocked || tile->blockingObjects.front()->ID == Obj::BOAT)) //and is water and is not blocked or is blocked by boat
+			if(tile->terType == ETerrainType::WATER  &&  (!tile->blocked || tile->blockingObjects.front()->ID == Obj::BOAT)) //and is water and is not blocked or is blocked by boat
 				return o->pos + offset;
 		}
 	}

+ 13 - 1
lib/mapObjects/CQuest.cpp

@@ -70,7 +70,7 @@ bool CQuest::checkQuest (const CGHeroInstance * h) const
 		case MISSION_ART:
 			for (auto & elem : m5arts)
 			{
-				if (h->hasArt(elem))
+				if (h->hasArt(elem, false, true))
 					continue;
 				return false; //if the artifact was not found
 			}
@@ -630,6 +630,18 @@ void CGSeerHut::finishQuest(const CGHeroInstance * h, ui32 accept) const
 			case CQuest::MISSION_ART:
 				for (auto & elem : quest->m5arts)
 				{
+					if(!h->hasArt(elem))
+					{
+						// first we need to disassemble this backpack artifact
+						auto assembly = h->getAssemblyByConstituent(elem);
+						assert(assembly);
+						for(auto & ci : assembly->constituentsInfo)
+						{
+							cb->giveHeroNewArtifact(h, ci.art->artType, ArtifactPosition::PRE_FIRST);
+						}
+						// remove the assembly
+						cb->removeArtifact(ArtifactLocation(h, h->getArtPos(assembly)));
+					}
 					cb->removeArtifact(ArtifactLocation(h, h->getArtPos(elem, false)));
 				}
 				break;

+ 1 - 1
lib/mapObjects/CRewardableObject.cpp

@@ -496,6 +496,7 @@ void CGPickable::initObj()
 					info.resize(1);
 					info[0].message.addTxt(MetaString::ADVOB_TXT, 51);
 					info[0].reward.removeObject = true;
+					break;
 			case 1:
 				{
 					info.resize(1);
@@ -682,7 +683,6 @@ void CGBonusingObject::initObj()
 			info[i].message.addTxt(MetaString::ADVOB_TXT, 62);
 			soundID = soundBase::experience;
 		}
-		onVisited.addTxt(MetaString::ADVOB_TXT, 63);
 		info.back().limiter.dayOfWeek = 7;
 		configureBonus(info.back(), Bonus::MORALE, 1, 68); // on last day of week
 		configureBonus(info.back(), Bonus::LUCK,   1, 68);

+ 32 - 0
lib/mapObjects/MiscObjects.cpp

@@ -393,6 +393,7 @@ void CGCreature::joinDecision(const CGHeroInstance *h, int cost, ui32 accept) co
 		if(cost)
 			cb->giveResource(h->tempOwner,Res::GOLD,-cost);
 
+		giveReward(h);
 		cb->tryJoiningArmy(this, h, true, true);
 	}
 }
@@ -456,6 +457,7 @@ void CGCreature::battleFinished(const CGHeroInstance *hero, const BattleResult &
 
 	if(result.winner==0)
 	{
+		giveReward(hero);
 		cb->removeObject(this);
 	}
 	else
@@ -558,6 +560,35 @@ int CGCreature::getNumberOfStacks(const CGHeroInstance *hero) const
 	return split;
 }
 
+void CGCreature::giveReward(const CGHeroInstance * h) const
+{
+	InfoWindow iw;
+	iw.player = h->tempOwner;
+
+	if(resources.size())
+	{
+		cb->giveResources(h->tempOwner, resources);
+		for(int i = 0; i < resources.size(); i++)
+		{
+			if(resources[i] > 0)
+				iw.components.push_back(Component(Component::RESOURCE, i, resources[i], 0));
+		}
+	}
+
+	if(gainedArtifact != ArtifactID::NONE)
+	{
+		cb->giveHeroNewArtifact(h, VLC->arth->artifacts[gainedArtifact], ArtifactPosition::FIRST_AVAILABLE);
+		iw.components.push_back(Component(Component::ARTIFACT, gainedArtifact, 0, 0));
+	}
+
+	if(iw.components.size())
+	{
+		iw.text.addTxt(MetaString::ADVOB_TXT, 183); // % has found treasure
+		iw.text.addReplacement(h->name);
+		cb->showInfoDialog(&iw);
+	}
+}
+
 void CGCreature::writeJsonOptions(JsonNode& json) const
 {
 
@@ -1791,6 +1822,7 @@ void CGMagi::onHeroVisit(const CGHeroInstance * h) const
 				cb->sendAndApply(&cv);
 			}
 			cv.pos = h->getPosition(false);
+			cv.focusTime = 0;
 			cb->sendAndApply(&cv);
 		}
 	}

+ 1 - 0
lib/mapObjects/MiscObjects.h

@@ -93,6 +93,7 @@ private:
 	void joinDecision(const CGHeroInstance *h, int cost, ui32 accept) const;
 
 	int takenAction(const CGHeroInstance *h, bool allowJoin=true) const; //action on confrontation: -2 - fight, -1 - flee, >=0 - will join for given value of gold (may be 0)
+	void giveReward(const CGHeroInstance * h) const;
 
 };
 

+ 2 - 0
lib/rmg/CMapGenerator.cpp

@@ -20,6 +20,8 @@ void CMapGenerator::foreach_neighbour(const int3 &pos, std::function<void(int3&
 	for(const int3 &dir : int3::getDirs())
 	{
 		int3 n = pos + dir;
+		/*important notice: perform any translation before this function is called,
+		so the actual map position is checked*/
 		if(map->isInTheMap(n))
 			foo(n);
 	}

+ 19 - 11
lib/rmg/CRmgTemplateZone.cpp

@@ -1176,25 +1176,23 @@ bool CRmgTemplateZone::createTreasurePile(CMapGenerator* gen, int3 &pos, float m
 
 		int3 closestTile = int3(-1,-1,-1);
 		float minDistance = 1e10;
+
 		for (auto visitablePos : info.visitableFromBottomPositions) //objects that are not visitable from top must be accessible from bottom or side
 		{
 			int3 closestFreeTile = findClosestTile(freePaths, visitablePos);
 			if (closestFreeTile.dist2d(visitablePos) < minDistance)
 			{
-				closestTile = visitablePos + int3 (0, 1, 0); //start below object (y+1), possibly even outside the map (?)
+				closestTile = visitablePos + int3 (0, 1, 0); //start below object (y+1), possibly even outside the map, to not make path up through it
 				minDistance = closestFreeTile.dist2d(visitablePos);
 			}
 		}
-		if (!closestTile.valid())
+		for (auto visitablePos : info.visitableFromTopPositions) //all objects are accessible from any direction
 		{
-			for (auto visitablePos : info.visitableFromTopPositions) //all objects are accessible from any direction
+			int3 closestFreeTile = findClosestTile(freePaths, visitablePos);
+			if (closestFreeTile.dist2d(visitablePos) < minDistance)
 			{
-				int3 closestFreeTile = findClosestTile(freePaths, visitablePos);
-				if (closestFreeTile.dist2d(visitablePos) < minDistance)
-				{
-					closestTile = visitablePos;
-					minDistance = closestFreeTile.dist2d(visitablePos);
-				}
+				closestTile = visitablePos;
+				minDistance = closestFreeTile.dist2d(visitablePos);
 			}
 		}
 		assert (closestTile.valid());
@@ -1245,6 +1243,8 @@ bool CRmgTemplateZone::createTreasurePile(CMapGenerator* gen, int3 &pos, float m
 			for (auto treasure : treasures)
 			{
 				int3 visitableOffset = treasure.second->getVisitableOffset();
+				if (treasure.second->ID == Obj::SEER_HUT) //FIXME: find generic solution or figure out why Seer Hut doesn't behave correctly
+					visitableOffset.x += 1;
 				placeObject(gen, treasure.second, treasure.first + visitableOffset);
 			}
 			if (addMonster(gen, guardPos, currentValue, false))
@@ -2661,8 +2661,7 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen)
 
 	//seer huts with creatures or generic rewards
 
-	//if (questArtZone) //we won't be placing seer huts if there is no zone left to place arties
-	if (false) //FIXME: Seer Huts are bugged
+	if (questArtZone) //we won't be placing seer huts if there is no zone left to place arties
 	{
 		static const int genericSeerHuts = 8;
 		int seerHutsPerType = 0;
@@ -2718,6 +2717,9 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen)
 				obj->quest->missionType = CQuest::MISSION_ART;
 				ArtifactID artid = *RandomGeneratorUtil::nextItem(gen->getQuestArtsRemaning(), gen->rand);
 				obj->quest->m5arts.push_back(artid);
+				obj->quest->lastDay = -1;
+				obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false;
+
 				gen->banQuestArt(artid);
 				gen->map->addQuest(obj);
 
@@ -2754,6 +2756,9 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen)
 				obj->quest->missionType = CQuest::MISSION_ART;
 				ArtifactID artid = *RandomGeneratorUtil::nextItem(gen->getQuestArtsRemaning(), gen->rand);
 				obj->quest->m5arts.push_back(artid);
+				obj->quest->lastDay = -1;
+				obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false;
+
 				gen->banQuestArt(artid);
 				gen->map->addQuest(obj);
 
@@ -2775,6 +2780,9 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen)
 				obj->quest->missionType = CQuest::MISSION_ART;
 				ArtifactID artid = *RandomGeneratorUtil::nextItem(gen->getQuestArtsRemaning(), gen->rand);
 				obj->quest->m5arts.push_back(artid);
+				obj->quest->lastDay = -1;
+				obj->quest->isCustomFirst = obj->quest->isCustomNext = obj->quest->isCustomComplete = false;
+
 				gen->banQuestArt(artid);
 				gen->map->addQuest(obj);
 

+ 17 - 5
server/CGameHandler.cpp

@@ -926,13 +926,16 @@ void CGameHandler::handleConnection(std::set<PlayerColor> players, CConnection &
 			{
 				const bool result = apply->applyOnGH(this,&c,pack, player);
 				if(!result)
-					complain("Got false in applying... that request must have been fishy!");
-                logGlobal->traceStream() << "Message successfully applied (result=" << result << ")!";
+				{
+					complain((boost::format("Got false in applying %s... that request must have been fishy!")
+				            % typeid(*pack).name()).str());
+				}
+				logGlobal->traceStream() << "Message successfully applied (result=" << result << ")!";
 				sendPackageResponse(true);
 			}
 			else
 			{
-                logGlobal->errorStream() << "Message cannot be applied, cannot find applier (unregistered type)!";
+				logGlobal->errorStream() << "Message cannot be applied, cannot find applier (unregistered type)!";
 				sendPackageResponse(false);
 			}
 
@@ -2113,7 +2116,6 @@ void CGameHandler::stopHeroVisitCastle(const CGTownInstance * obj, const CGHeroI
 
 void CGameHandler::removeArtifact(const ArtifactLocation &al)
 {
-	assert(al.getArt());
 	EraseArtifact ea;
 	ea.al = al;
 	sendAndApply(&ea);
@@ -3038,7 +3040,7 @@ bool CGameHandler::assembleArtifacts (ObjectInstanceID heroID, ArtifactPosition
 		sendAndApply(&da);
 	}
 
-	return false;
+	return true;
 }
 
 bool CGameHandler::buyArtifact( ObjectInstanceID hid, ArtifactID aid )
@@ -4026,6 +4028,16 @@ void CGameHandler::playerMessage( PlayerColor player, const std::string &message
 			if(!hero->hasStackAtSlot(SlotID(i)))
 				insertNewStack(StackLocation(hero, SlotID(i)), blackKnight, 10);
 	}
+	else if(message == "vcmiglaurung") //gives 5000 crystal dragons into each slot
+	{
+		CGHeroInstance *hero = gs->getHero(currObj);
+		const CCreature *crystalDragon = VLC->creh->creatures.at(133);
+		if(!hero) return;
+
+		for(int i = 0; i < GameConstants::ARMY_SIZE; i++)
+			if(!hero->hasStackAtSlot(SlotID(i)))
+				insertNewStack(StackLocation(hero, SlotID(i)), crystalDragon, 5000);
+	}
 	else if(message == "vcminoldor") //all war machines
 	{
 		CGHeroInstance *hero = gs->getHero(currObj);

+ 2 - 1
server/CQuery.cpp

@@ -321,7 +321,8 @@ CBlockingDialogQuery::CBlockingDialogQuery(const BlockingDialog &bd)
 
 void CTeleportDialogQuery::notifyObjectAboutRemoval(const CObjectVisitQuery &objectVisit) const
 {
-	auto obj = dynamic_ptr_cast<const CGTeleport>(objectVisit.visitedObject);
+	// do not change to dynamic_ptr_cast - SIGSEGV!
+	auto obj = dynamic_cast<const CGTeleport*>(objectVisit.visitedObject);
 	obj->teleportDialogAnswered(objectVisit.visitingHero, *answer, td.exits);
 }