浏览代码

Rearragned Seer Huts & Quests. More work on Quest Log descriptions.

DjWarmonger 13 年之前
父节点
当前提交
7fbf89ffc8
共有 6 个文件被更改,包括 468 次插入358 次删除
  1. 34 11
      client/CQuestLog.cpp
  2. 4 3
      client/CQuestLog.h
  3. 79 37
      client/UIFramework/CIntObjectClasses.cpp
  4. 18 7
      client/UIFramework/CIntObjectClasses.h
  5. 319 287
      lib/CObjectHandler.cpp
  6. 14 13
      lib/CObjectHandler.h

+ 34 - 11
client/CQuestLog.cpp

@@ -21,6 +21,16 @@
 #include "UIFramework/CGuiHandler.h"
 #include "UIFramework/CIntObjectClasses.h"
 
+/*
+ * CQuestLog.cpp, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
 struct QuestInfo;
 class CAdvmapInterface;
 
@@ -30,6 +40,12 @@ void CQuestLabel::clickLeft(tribool down, bool previousState)
 		callback();
 }
 
+void CQuestLabel::showAll(SDL_Surface * to)
+{
+	if (active)
+		CBoundedLabel::showAll (to);
+}
+
 void CQuestMinimap::clickLeft(tribool down, bool previousState)
 {
 	if (down)
@@ -58,11 +74,11 @@ CQuestLog::CQuestLog (const std::vector<QuestInfo> & Quests) :
 void CQuestLog::init()
 {
 	minimap = new CQuestMinimap (Rect (47, 33, 144, 144));
-	description = new CTextBox ("", Rect(240, 33, 355, 355), 1, FONT_SMALL, TOPLEFT, Colors::Cornsilk);
+	description = new CTextBox ("", Rect(245, 33, 350, 355), 1, FONT_MEDIUM, TOPLEFT, Colors::Cornsilk);
 	ok = new CAdventureMapButton("",CGI->generaltexth->zelp[445].second, boost::bind(&CQuestLog::close,this), 547, 401, "IOKAY.DEF", SDLK_RETURN);
 
 	if (quests.size() > QUEST_COUNT)
-		slider = new CSlider(203, 199, 230, boost::bind (&CQuestLog::sliderMoved, this, _1), quests.size(), quests.size(), false, 0);
+		slider = new CSlider(203, 199, 230, boost::bind (&CQuestLog::sliderMoved, this, _1), QUEST_COUNT, quests.size(), false, 0);
 
 	auto map = LOCPLINT->cb->getVisibilityMap(); //TODO: another function to get all tiles?
 
@@ -73,26 +89,31 @@ void CQuestLog::init()
 
 	for (int i = 0; i < quests.size(); ++i)
 	{
-		CQuestLabel * label = new CQuestLabel (28, 199 + i * 24, FONT_SMALL, TOPLEFT, Colors::Cornsilk, quests[i].quest.firstVisitText);
+		MetaString text;
+		quests[i].quest.getRolloverText (text, false);
+		if (quests[i].obj)
+			text.addReplacement (quests[i].obj->getHoverText()); //get name of the object
+		CQuestLabel * label = new CQuestLabel (28, 199 + i * 24, FONT_SMALL, TOPLEFT, Colors::Cornsilk, text.toString());
 		label->callback = boost::bind(&CQuestLog::selectQuest, this, i);
+		label->setBounds (172, 30);
 		labels.push_back(label);
 	}
 
-	recreateQuestList (0); //truncate invisible and too wide labels
+	recreateQuestList (0);
 	showAll (screen2);
 }
 
 void CQuestLog::showAll(SDL_Surface * to)
 {
 	CIntObject::showAll (to);
-	recreateQuestList (0);
 	BOOST_FOREACH (auto label, labels)
 	{
-		if (label->active)
-			label->show(to);
+		label->show(to); //shows only if active
 	}
-	if (labels.size())
+	if (labels.size() && labels[questIndex]->active)
+	{
 		CSDL_Ext::drawBorder(to, Rect::around(labels[questIndex]->pos), int3(Colors::MetallicGold.r, Colors::MetallicGold.g, Colors::MetallicGold.b));
+	}
 	description->show(to);
 	minimap->update();
 }
@@ -101,9 +122,9 @@ void CQuestLog::recreateQuestList (int newpos)
 {
 	for (int i = 0; i < labels.size(); ++i)
 	{
+		labels[i]->pos = Rect (pos.x + 28, pos.y + 207 + (i-newpos) * 25, 173, 23);
 		if (i >= newpos && i < newpos + QUEST_COUNT)
 		{
-			labels[i]->pos = Rect (pos.x + 28, pos.y + 207 + (i-newpos) * 24, 172, 30); //TODO: limit label width?
 			labels[i]->activate();
 		}
 		else
@@ -120,10 +141,12 @@ void CQuestLog::selectQuest (int which)
 	minimap->currentQuest = currentQuest;
 	if (currentQuest->obj)
 	{
-		//minimap->setLevel (currentQuest->obj->pos.z);
 		adventureInt->centerOn (currentQuest->obj->pos);
 	}
-	description->text = currentQuest->quest.firstVisitText; //TODO: use special log entry text
+	MetaString text;
+	std::vector<Component> components; //TODO: display them
+	currentQuest->quest.getVisitText (text, components , currentQuest->quest.isCustomFirst, true);
+	description->setTxt (text.toString()); //TODO: use special log entry text
 	redraw();
 }
 

+ 4 - 3
client/CQuestLog.h

@@ -6,7 +6,7 @@
 #include "../lib/CGameState.h"
 
 /*
- * CCreatureWindow.h, part of VCMI engine
+ * CQuestLog.h, part of VCMI engine
  *
  * Authors: listed in file AUTHORS in main folder
  *
@@ -33,14 +33,15 @@ extern CAdvMapInt *adventureInt;
 
 const int QUEST_COUNT = 9;
 
-class CQuestLabel : public LRClickableAreaWText, public CLabel
+class CQuestLabel : public LRClickableAreaWText, public CBoundedLabel
 {
 public:
 	boost::function<void()> callback;
 
 	CQuestLabel (int x=0, int y=0, EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT, const SDL_Color &Color = Colors::Cornsilk, const std::string &Text =  "")
-		: CLabel (x, y, FONT_SMALL, TOPLEFT, Colors::Cornsilk, Text){};
+		: CBoundedLabel (x, y, FONT_SMALL, TOPLEFT, Colors::Cornsilk, Text){};
 	void clickLeft(tribool down, bool previousState);
+	void showAll(SDL_Surface * to);
 };
 
 class CQuestMinimap : public CMinimap

+ 79 - 37
client/UIFramework/CIntObjectClasses.cpp

@@ -1234,6 +1234,71 @@ void CLabel::setTxt(const std::string &Txt)
 	}
 }
 
+void CBoundedLabel::setBounds(int limitW, int limitH)
+{
+	pos.h = limitH;
+	pos.w = limitW;
+	recalculateLines(text);
+}
+
+void CBoundedLabel::setTxt(const std::string &Txt)
+{
+	recalculateLines(Txt);
+	CLabel::setTxt(Txt);
+}
+
+void CBoundedLabel::showAll(SDL_Surface * to)
+{
+	CIntObject::showAll(to);
+
+	const Font &f = *graphics->fonts[font];
+	int lineHeight =  f.height; 
+	int lineCapacity = pos.h / lineHeight;
+
+	int dy = f.height; //line height
+	int base_y = pos.y;
+	if(alignment == CENTER)
+		base_y += std::max((pos.h - maxH)/2,0);
+
+	for (int i = 0; i < lineCapacity; i++)
+	{
+		const std::string &line = lines[i];
+		if(!line.size()) continue;
+
+		int x = pos.x;
+		if(alignment == CENTER)
+		{
+			x += (pos.w - f.getWidth(line.c_str())) / 2;
+		}
+
+		if(line[0] == '{' && line[line.size()-1] == '}')
+			CSDL_Ext::printAt(line, x, base_y + i*dy, font, Colors::Jasmine, to);
+		else
+			CSDL_Ext::printAt(line, x, base_y + i*dy, font, color, to);
+	}
+}
+
+void CBoundedLabel::recalculateLines(const std::string &Txt)
+{
+	lines.clear();
+
+	const Font &f = *graphics->fonts[font];
+	int lineHeight =  f.height; 
+	int lineCapacity = pos.h / lineHeight;
+
+	lines = CMessage::breakText(Txt, pos.w, font);
+	if(lines.size() > lineCapacity) //we need to add a slider
+	{
+		lines = CMessage::breakText(Txt, pos.w - 32 - 10, font);
+		OBJ_CONSTRUCTION_CAPTURING_ALL;
+	}
+
+	maxH = lineHeight * lines.size();
+	maxW = 0;
+	BOOST_FOREACH(const std::string &line, lines)
+		vstd::amax(maxW, f.getWidth(line.c_str()));
+}
+
 CLabelGroup::CLabelGroup(EFonts Font, EAlignment Align, const SDL_Color &Color):
 	font(Font), align(Align), color(Color)
 {};
@@ -1245,7 +1310,7 @@ void CLabelGroup::add(int x, int y, const std::string &text)
 };
 
 CTextBox::CTextBox(std::string Text, const Rect &rect, int SliderStyle, EFonts Font /*= FONT_SMALL*/, EAlignment Align /*= TOPLEFT*/, const SDL_Color &Color /*= Colors::Cornsilk*/)
-:CLabel(rect.x, rect.y, Font, Align, Color, Text), sliderStyle(SliderStyle), slider(NULL)
+:CBoundedLabel(rect.x, rect.y, Font, Align, Color, Text), sliderStyle(SliderStyle), slider(NULL)
 {
 	type |= REDRAW_PARENT;
 	autoRedraw = false;
@@ -1256,6 +1321,19 @@ CTextBox::CTextBox(std::string Text, const Rect &rect, int SliderStyle, EFonts F
 	setTxt(Text);
 }
 
+void CTextBox::recalculateLines(const std::string &Txt)
+{
+	CBoundedLabel::recalculateLines (Txt);
+
+	const Font &f = *graphics->fonts[font];
+	int lineHeight =  f.height; 
+	int lineCapacity = pos.h / lineHeight;
+
+	vstd::clear_pointer(slider);
+	if (lines.size() > lineCapacity) //we need to add a slider
+		slider = new CSlider(pos.w - 32, 0, pos.h, boost::bind(&CTextBox::sliderMoved, this, _1), lineCapacity, lines.size(), 0, false, sliderStyle);
+}
+
 void CTextBox::showAll(SDL_Surface * to)
 {
 	CIntObject::showAll(to);
@@ -1290,12 +1368,6 @@ void CTextBox::showAll(SDL_Surface * to)
 
 }
 
-void CTextBox::setTxt(const std::string &Txt)
-{
-	recalculateLines(Txt);
-	CLabel::setTxt(Txt);
-}
-
 void CTextBox::sliderMoved(int to)
 {
 	if(!slider)
@@ -1304,36 +1376,6 @@ void CTextBox::sliderMoved(int to)
 	redraw();
 }
 
-void CTextBox::setBounds(int limitW, int limitH)
-{
-	pos.h = limitH;
-	pos.w = limitW;
-	recalculateLines(text);
-}
-
-void CTextBox::recalculateLines(const std::string &Txt)
-{
-	vstd::clear_pointer(slider);
-	lines.clear();
-
-	const Font &f = *graphics->fonts[font];
-	int lineHeight =  f.height; 
-	int lineCapacity = pos.h / lineHeight;
-
-	lines = CMessage::breakText(Txt, pos.w, font);
-	if(lines.size() > lineCapacity) //we need to add a slider
-	{
-		lines = CMessage::breakText(Txt, pos.w - 32 - 10, font);
-		OBJ_CONSTRUCTION_CAPTURING_ALL;
-		slider = new CSlider(pos.w - 32, 0, pos.h, boost::bind(&CTextBox::sliderMoved, this, _1), lineCapacity, lines.size(), 0, false, sliderStyle);
-	}
-
-	maxH = lineHeight * lines.size();
-	maxW = 0;
-	BOOST_FOREACH(const std::string &line, lines)
-		vstd::amax(maxW, f.getWidth(line.c_str()));
-}
-
 void CGStatusBar::print(const std::string & Text)
 {
 	setTxt(Text);

+ 18 - 7
client/UIFramework/CIntObjectClasses.h

@@ -339,6 +339,23 @@ public:
 	CLabel(int x=0, int y=0, EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT, const SDL_Color &Color = Colors::Cornsilk, const std::string &Text =  "");
 };
 
+class CBoundedLabel : public CLabel
+{
+public:
+
+	int maxW; //longest line of text in px
+	int maxH; //total height needed to print all lines
+
+	std::vector<std::string> lines;
+
+	CBoundedLabel(int x=0, int y=0, EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT, const SDL_Color &Color = Colors::Cornsilk, const std::string &Text =  "")
+		: CLabel (x, y, Font, Align, Color, Text){};
+	void setTxt(const std::string &Txt);
+	void setBounds(int limitW, int limitH);
+	void recalculateLines(const std::string &Txt);
+	void showAll(SDL_Surface * to);
+};
+
 //Small helper class to manage group of similar labels 
 class CLabelGroup : public CIntObject
 {
@@ -352,24 +369,18 @@ public:
 };
 
 /// a multi-line label that tries to fit text with given available width and height; if not possible, it creates a slider for scrolling text
-class CTextBox
-	: public CLabel
+class CTextBox : public CBoundedLabel
 {
 public:
-	int maxW; //longest line of text in px
-	int maxH; //total height needed to print all lines
 
 	int sliderStyle;
 
-	std::vector<std::string> lines;
 	std::vector<CAnimImage* > effects;
 	CSlider *slider;
 
 	//CTextBox( std::string Text, const Point &Pos, int w, int h, EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT, const SDL_Color &Color = Colors::Cornsilk);
 	CTextBox(std::string Text, const Rect &rect, int SliderStyle, EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT, const SDL_Color &Color = Colors::Cornsilk);
 	void showAll(SDL_Surface * to); //shows statusbar (with current text)
-	void setTxt(const std::string &Txt);
-	void setBounds(int limitW, int limitH);
 	void recalculateLines(const std::string &Txt);
 
 	void sliderMoved(int to);

+ 319 - 287
lib/CObjectHandler.cpp

@@ -4121,6 +4121,275 @@ bool CQuest::checkQuest (const CGHeroInstance * h) const
 			return false;
 	}
 }
+void CQuest::getVisitText (MetaString &iwText, std::vector<Component> &components, bool isCustom, bool firstVisit, const CGHeroInstance * h) const
+{
+	std::string text;
+	bool failRequirements = (h ? !checkQuest(h) : true);
+
+	if (firstVisit) 
+	{
+		isCustom = isCustomFirst;
+		iwText << firstVisitText;
+	}
+	else if (failRequirements)
+	{
+		isCustom = isCustomNext;
+		iwText << nextVisitText;
+	}
+	switch (missionType)
+	{
+		case MISSION_LEVEL:
+		components.push_back(Component (Component::EXPERIENCE, 1, m13489val, 0));
+		if (!isCustom)
+			iwText.addReplacement(m13489val);
+		break;
+		case MISSION_PRIMARY_STAT:
+		{
+			MetaString loot;
+			for (int i = 0; i < 4; ++i)
+			{
+				if (m2stats[i])
+				{
+					components.push_back(Component (Component::PRIM_SKILL, i, m2stats[i], 0));
+					loot << "%d %s";
+					loot.addReplacement(m2stats[i]);
+					loot.addReplacement(VLC->generaltexth->primarySkillNames[i]);
+				}		
+			}
+			if (!isCustom)
+				iwText.addReplacement(loot.buildList());
+		}
+			break;
+		case MISSION_KILL_HERO:
+			components.push_back(Component(Component::HERO, heroPortrait, 0, 0));
+			if (!isCustom)
+				addReplacements(iwText, text);
+			break;
+		case MISSION_HERO:
+			components.push_back(Component (Component::HERO, m13489val, 0, 0));
+			if (!isCustom)
+				iwText.addReplacement(VLC->heroh->heroes[m13489val]->name);
+			break;
+		case MISSION_KILL_CREATURE:
+			{
+				components.push_back(Component(stackToKill));
+				if (!isCustom)
+					addReplacements(iwText, text);
+			}
+			break;
+		case MISSION_ART:
+		{
+			MetaString loot;
+			for (std::vector<ui16>::const_iterator it = m5arts.begin(); it != m5arts.end(); ++it)
+			{
+				components.push_back(Component (Component::ARTIFACT, *it, 0, 0));
+				loot << "%s";
+				loot.addReplacement(MetaString::ART_NAMES, *it);
+			}
+			if (!isCustom)
+				iwText.addReplacement(loot.buildList());
+		}
+			break;
+		case MISSION_ARMY:
+		{
+			MetaString loot;
+			for (std::vector<CStackBasicDescriptor>::const_iterator it = m6creatures.begin(); it != m6creatures.end(); ++it)
+			{
+				components.push_back(Component(*it));
+				loot << "%s";
+				loot.addReplacement(*it);
+			}
+			if (!isCustom)
+				iwText.addReplacement(loot.buildList());
+		}
+			break;
+		case MISSION_RESOURCES:
+		{
+			MetaString loot;
+			for (int i = 0; i < 7; ++i)
+			{
+				if (m7resources[i])
+				{
+					components.push_back(Component (Component::RESOURCE, i, m7resources[i], 0));
+					loot << "%d %s";
+					loot.addReplacement(m7resources[i]);
+					loot.addReplacement(MetaString::RES_NAMES, i);
+				}
+			}
+			if (!isCustom)
+				iwText.addReplacement(loot.buildList());
+		}
+			break;
+		case MISSION_PLAYER:
+			components.push_back(Component (Component::FLAG, m13489val, 0, 0));
+			if (!isCustom)
+				iwText.addReplacement(VLC->generaltexth->colors[m13489val]);
+			break;
+	}
+}
+void CQuest::getRolloverText (MetaString &ms, bool onHover) const
+{
+	if (onHover)
+		ms << "\n\n";
+
+	ms << VLC->generaltexth->quests[missionType-1][onHover ? 3 : 4][textOption];
+
+	switch (missionType)
+	{
+		case MISSION_LEVEL:
+			ms.addReplacement(m13489val);
+			break;
+		case MISSION_PRIMARY_STAT:
+			{
+				MetaString loot;
+				for (int i = 0; i < 4; ++i)
+				{
+					if (m2stats[i])
+					{
+						loot << "%d %s";
+						loot.addReplacement(m2stats[i]);
+						loot.addReplacement(VLC->generaltexth->primarySkillNames[i]);
+					}		
+				}
+				ms.addReplacement(loot.buildList());
+			}
+			break;
+		case MISSION_KILL_HERO:
+			ms.addReplacement(heroName);
+			break;
+		case MISSION_KILL_CREATURE:
+			ms.addReplacement(stackToKill);
+			break;
+		case MISSION_ART:
+			{
+				MetaString loot;
+				for (std::vector<ui16>::const_iterator it = m5arts.begin(); it != m5arts.end(); ++it)
+				{
+					loot << "%s";
+					loot.addReplacement(MetaString::ART_NAMES, *it);
+				}
+				ms.addReplacement(loot.buildList());
+			}
+			break;
+		case MISSION_ARMY:
+			{
+				MetaString loot;
+				for (std::vector<CStackBasicDescriptor>::const_iterator it = m6creatures.begin(); it != m6creatures.end(); ++it)
+				{
+					loot << "%s";
+					loot.addReplacement(*it);
+				}
+				ms.addReplacement(loot.buildList());
+			}
+			break;
+		case MISSION_RESOURCES:
+			{
+				MetaString loot;
+				for (int i = 0; i < 7; ++i)
+				{
+					if (m7resources[i])
+					{
+						loot << "%d %s";
+						loot.addReplacement(m7resources[i]);
+						loot.addReplacement(MetaString::RES_NAMES, i);
+					}
+				}
+				ms.addReplacement(loot.buildList());
+			}
+			break;
+		case MISSION_HERO:
+			ms.addReplacement(VLC->heroh->heroes[m13489val]->name);
+			break;
+		case MISSION_PLAYER:
+			ms.addReplacement(VLC->generaltexth->colors[m13489val]);
+			break;
+		default:
+			break;
+	}
+}
+
+void CQuest::getCompletionText (MetaString &iwText, std::vector<Component> &components, bool isCustom, const CGHeroInstance * h) const
+{
+	iwText << completedText;
+	switch (missionType)
+	{
+		case CQuest::MISSION_LEVEL:
+			if (!isCustomComplete)
+				iwText.addReplacement(m13489val);
+			break;
+		case CQuest::MISSION_PRIMARY_STAT:
+			if (vstd::contains (completedText,'%')) //there's one case when there's nothing to replace
+			{
+				MetaString loot;
+				for (int i = 0; i < 4; ++i)
+				{
+					if (m2stats[i])
+					{
+						loot << "%d %s";
+						loot.addReplacement(m2stats[i]);
+						loot.addReplacement(VLC->generaltexth->primarySkillNames[i]);
+					}
+				}
+				if (!isCustomComplete)
+					iwText.addReplacement(loot.buildList());
+			}
+			break;
+		case CQuest::MISSION_ART:
+		{
+			MetaString loot;
+			for (std::vector<ui16>::const_iterator it = m5arts.begin(); it != m5arts.end(); ++it)
+			{
+				loot << "%s";
+				loot.addReplacement(MetaString::ART_NAMES, *it);
+			}
+			if (!isCustomComplete)
+				iwText.addReplacement(loot.buildList());
+		}
+			break;
+		case CQuest::MISSION_ARMY:
+		{
+			MetaString loot;
+			for (std::vector<CStackBasicDescriptor>::const_iterator it = m6creatures.begin(); it != m6creatures.end(); ++it)
+			{
+				loot << "%s";
+				loot.addReplacement(*it);
+			}
+			if (!isCustomComplete)
+				iwText.addReplacement(loot.buildList());
+		}
+			break;
+		case CQuest::MISSION_RESOURCES:
+		{
+			MetaString loot;
+			for (int i = 0; i < 7; ++i)
+			{
+				if (m7resources[i])
+				{
+					loot << "%d %s";
+					loot.addReplacement(m7resources[i]);
+					loot.addReplacement(MetaString::RES_NAMES, i);
+				}
+			}
+			if (!isCustomComplete)
+				iwText.addReplacement(loot.buildList());
+		}
+			break;
+		case MISSION_KILL_HERO:
+		case MISSION_KILL_CREATURE:
+			if (!isCustomComplete)
+				addReplacements(iwText, completedText);
+			break;
+		case MISSION_HERO:
+			if (!isCustomComplete)
+				iwText.addReplacement(VLC->heroh->heroes[m13489val]->name);
+			break;
+		case MISSION_PLAYER:
+			if (!isCustomComplete)
+				iwText.addReplacement(VLC->generaltexth->colors[m13489val]);
+			break;
+	}
+}
+
 void CGSeerHut::initObj()
 {
 	seerName = VLC->generaltexth->seerNames[ran()%VLC->generaltexth->seerNames.size()];
@@ -4168,84 +4437,55 @@ const std::string & CGSeerHut::getHoverText() const
 	if (progress & missionType) //rollover when the quest is active
 	{
 		MetaString ms;
-		ms << "\n\n" << VLC->generaltexth->quests[missionType-1][3][textOption];
-		std::string str;
-		switch (missionType)
+		getRolloverText (ms, true); 
+		hoverName += ms.toString();
+	}
+	return hoverName;
+}
+
+void CQuest::addReplacements(MetaString &out, const std::string &base) const
+{
+	switch(missionType)
+	{
+	case MISSION_KILL_CREATURE:
+		out.addReplacement(stackToKill);
+		if (std::count(base.begin(), base.end(), '%') == 2) //say where is placed monster
 		{
-			case MISSION_LEVEL:
-				ms.addReplacement(m13489val);
-				break;
-			case MISSION_PRIMARY_STAT:
-				{
-					MetaString loot;
-					for (int i = 0; i < 4; ++i)
-					{
-						if (m2stats[i])
-						{
-							loot << "%d %s";
-							loot.addReplacement(m2stats[i]);
-							loot.addReplacement(VLC->generaltexth->primarySkillNames[i]);
-						}		
-					}
-					ms.addReplacement(loot.buildList());
-				}
-				break;
-			case MISSION_KILL_HERO:
-				ms.addReplacement(heroName);
-				break;
-			case MISSION_KILL_CREATURE:
-				ms.addReplacement(stackToKill);
-				break;
-			case MISSION_ART:
-				{
-					MetaString loot;
-					for (std::vector<ui16>::const_iterator it = m5arts.begin(); it != m5arts.end(); ++it)
-					{
-						loot << "%s";
-						loot.addReplacement(MetaString::ART_NAMES, *it);
-					}
-					ms.addReplacement(loot.buildList());
-				}
-				break;
-			case MISSION_ARMY:
-				{
-					MetaString loot;
-					for (std::vector<CStackBasicDescriptor>::const_iterator it = m6creatures.begin(); it != m6creatures.end(); ++it)
-					{
-						loot << "%s";
-						loot.addReplacement(*it);
-					}
-					ms.addReplacement(loot.buildList());
-				}
-				break;
-			case MISSION_RESOURCES:
-				{
-					MetaString loot;
-					for (int i = 0; i < 7; ++i)
-					{
-						if (m7resources[i])
-						{
-							loot << "%d %s";
-							loot.addReplacement(m7resources[i]);
-							loot.addReplacement(MetaString::RES_NAMES, i);
-						}
-					}
-					ms.addReplacement(loot.buildList());
-				}
-				break;
-			case MISSION_HERO:
-				ms.addReplacement(VLC->heroh->heroes[m13489val]->name);
-				break;
-			case MISSION_PLAYER:
-				ms.addReplacement(VLC->generaltexth->colors[m13489val]);
-				break;
-			default:
-				break;
+			out.addReplacement(VLC->generaltexth->arraytxt[147+stackDirection]);
 		}
-		ms.toString(str);
-		hoverName += str; 
+		break;
+	case MISSION_KILL_HERO:
+		out.addReplacement(heroName);
+		break;
+	}
+}
+
+void CGSeerHut::getCompletionText(MetaString &text, std::vector<Component> &components, bool isCustom, const CGHeroInstance * h) const
+{
+	CQuest::getCompletionText (text, components, isCustom, h);
+	switch (rewardType)
+	{
+		case 1: components.push_back(Component (Component::EXPERIENCE, 0, rVal*(100+h->getSecSkillLevel(CGHeroInstance::LEARNING)*5)/100.0, 0));
+			break;
+		case 2: components.push_back(Component (Component::PRIM_SKILL, 5, rVal, 0));
+			break;
+		case 3: components.push_back(Component (Component::MORALE, 0, rVal, 0));
+			break;
+		case 4: components.push_back(Component (Component::LUCK, 0, rVal, 0));
+			break;
+		case 5: components.push_back(Component (Component::RESOURCE, rID, rVal, 0));
+			break;
+		case 6: components.push_back(Component (Component::PRIM_SKILL, rID, rVal, 0));
+			break;
+		case 7: components.push_back(Component (Component::SEC_SKILL, rID, rVal, 0));
+			break;
+		case 8: components.push_back(Component (Component::ARTIFACT, rID, 0, 0));
+			break;
+		case 9: components.push_back(Component (Component::SPELL, rID, 0, 0));
+			break;
+		case 10: components.push_back(Component (Component::CREATURE, rID, rVal, 0));
+			break;
 	}
-	return hoverName;
 }
 
 void CGSeerHut::setPropertyDer (ui8 what, ui32 val)
@@ -4278,11 +4518,10 @@ void CGSeerHut::onHeroVisit( const CGHeroInstance * h ) const
 		bool firstVisit = !progress;
 		bool failRequirements = !checkQuest(h);
 		bool isCustom=false;
-		std::string text;
+
 		if (firstVisit) 
 		{
 			isCustom = isCustomFirst;
-			text = firstVisitText;
 			cb->setObjProperty (id, 10, 1);
 
 			AddQuest aq;
@@ -4293,102 +4532,12 @@ void CGSeerHut::onHeroVisit( const CGHeroInstance * h ) const
 		else if (failRequirements)
 		{
 			isCustom = isCustomNext;
-			text = nextVisitText;
 		}
-		iw.text << text;
 
 		if (firstVisit || failRequirements)
 		{
-			switch (missionType)
-			{
-				case MISSION_LEVEL:
-					iw.components.push_back(Component (Component::EXPERIENCE, 1, m13489val, 0));
-					if (!isCustom)
-						iw.text.addReplacement(m13489val);
-					break;
-				case MISSION_PRIMARY_STAT:
-				{
-					MetaString loot;
-					for (int i = 0; i < 4; ++i)
-					{
-						if (m2stats[i])
-						{
-							iw.components.push_back(Component (Component::PRIM_SKILL, i, m2stats[i], 0));
-							loot << "%d %s";
-							loot.addReplacement(m2stats[i]);
-							loot.addReplacement(VLC->generaltexth->primarySkillNames[i]);
-						}		
-					}
-					if (!isCustom)
-						iw.text.addReplacement(loot.buildList());
-				}
-					break;
-				case MISSION_KILL_HERO:
-					iw.components.push_back(Component(Component::HERO, heroPortrait, 0, 0));
-					if (!isCustom)
-						addReplacements(iw.text, text);
-					break;
-				case MISSION_HERO:
-					iw.components.push_back(Component (Component::HERO, m13489val, 0, 0));
-					if (!isCustom)
-						iw.text.addReplacement(VLC->heroh->heroes[m13489val]->name);
-					break;
-				case MISSION_KILL_CREATURE:
-					{
-						iw.components.push_back(Component(stackToKill));
-						if (!isCustom)
-							addReplacements(iw.text, text);
-					}
-					break;
-				case MISSION_ART:
-				{
-					MetaString loot;
-					for (std::vector<ui16>::const_iterator it = m5arts.begin(); it != m5arts.end(); ++it)
-					{
-						iw.components.push_back(Component (Component::ARTIFACT, *it, 0, 0));
-						loot << "%s";
-						loot.addReplacement(MetaString::ART_NAMES, *it);
-					}
-					if (!isCustom)
-						iw.text.addReplacement(loot.buildList());
-				}
-					break;
-				case MISSION_ARMY:
-				{
-					MetaString loot;
-					for (std::vector<CStackBasicDescriptor>::const_iterator it = m6creatures.begin(); it != m6creatures.end(); ++it)
-					{
-						iw.components.push_back(Component(*it));
-						loot << "%s";
-						loot.addReplacement(*it);
-					}
-					if (!isCustom)
-						iw.text.addReplacement(loot.buildList());
-				}
-					break;
-				case MISSION_RESOURCES:
-				{
-					MetaString loot;
-					for (int i = 0; i < 7; ++i)
-					{
-						if (m7resources[i])
-						{
-							iw.components.push_back(Component (Component::RESOURCE, i, m7resources[i], 0));
-							loot << "%d %s";
-							loot.addReplacement(m7resources[i]);
-							loot.addReplacement(MetaString::RES_NAMES, i);
-						}
-					}
-					if (!isCustom)
-						iw.text.addReplacement(loot.buildList());
-				}
-					break;
-				case MISSION_PLAYER:
-					iw.components.push_back(Component (Component::FLAG, m13489val, 0, 0));
-					if (!isCustom)
-						iw.text.addReplacement(VLC->generaltexth->colors[m13489val]);
-					break;
-			}
+			getVisitText (iw.text, iw.components, isCustom, firstVisit, h);
+
 			cb->showInfoDialog(&iw);
 		}
 		if (!failRequirements) // propose completion, also on first visit
@@ -4396,108 +4545,8 @@ void CGSeerHut::onHeroVisit( const CGHeroInstance * h ) const
 			BlockingDialog bd (true, false);
 			bd.player = h->getOwner();
 			bd.soundID = soundBase::QUEST;
-			bd.text << completedText;
-			switch (missionType)
-			{
-				case CQuest::MISSION_LEVEL:
-					if (!isCustomComplete)
-						bd.text.addReplacement(m13489val);
-					break;
-				case CQuest::MISSION_PRIMARY_STAT:
-					if (vstd::contains (completedText,'%')) //there's one case when there's nothing to replace
-					{
-						MetaString loot;
-						for (int i = 0; i < 4; ++i)
-						{
-							if (m2stats[i])
-							{
-								loot << "%d %s";
-								loot.addReplacement(m2stats[i]);
-								loot.addReplacement(VLC->generaltexth->primarySkillNames[i]);
-							}
-						}
-						if (!isCustomComplete)
-							bd.text.addReplacement(loot.buildList());
-					}
-					break;
-				case CQuest::MISSION_ART:
-				{
-					MetaString loot;
-					for (std::vector<ui16>::const_iterator it = m5arts.begin(); it != m5arts.end(); ++it)
-					{
-						loot << "%s";
-						loot.addReplacement(MetaString::ART_NAMES, *it);
-					}
-					if (!isCustomComplete)
-						bd.text.addReplacement(loot.buildList());
-				}
-					break;
-				case CQuest::MISSION_ARMY:
-				{
-					MetaString loot;
-					for (std::vector<CStackBasicDescriptor>::const_iterator it = m6creatures.begin(); it != m6creatures.end(); ++it)
-					{
-						loot << "%s";
-						loot.addReplacement(*it);
-					}
-					if (!isCustomComplete)
-						bd.text.addReplacement(loot.buildList());
-				}
-					break;
-				case CQuest::MISSION_RESOURCES:
-				{
-					MetaString loot;
-					for (int i = 0; i < 7; ++i)
-					{
-						if (m7resources[i])
-						{
-							loot << "%d %s";
-							loot.addReplacement(m7resources[i]);
-							loot.addReplacement(MetaString::RES_NAMES, i);
-						}
-					}
-					if (!isCustomComplete)
-						bd.text.addReplacement(loot.buildList());
-				}
-					break;
-				case MISSION_KILL_HERO:
-				case MISSION_KILL_CREATURE:
-					if (!isCustomComplete)
-						addReplacements(bd.text, completedText);
-					break;
-				case MISSION_HERO:
-					if (!isCustomComplete)
-						bd.text.addReplacement(VLC->heroh->heroes[m13489val]->name);
-					break;
-				case MISSION_PLAYER:
-					if (!isCustomComplete)
-						bd.text.addReplacement(VLC->generaltexth->colors[m13489val]);
-					break;
-			}
 			
-			switch (rewardType)
-			{
-				case 1: bd.components.push_back(Component (Component::EXPERIENCE, 0, rVal*(100+h->getSecSkillLevel(CGHeroInstance::LEARNING)*5)/100.0, 0));
-					break;
-				case 2: bd.components.push_back(Component (Component::PRIM_SKILL, 5, rVal, 0));
-					break;
-				case 3: bd.components.push_back(Component (Component::MORALE, 0, rVal, 0));
-					break;
-				case 4: bd.components.push_back(Component (Component::LUCK, 0, rVal, 0));
-					break;
-				case 5: bd.components.push_back(Component (Component::RESOURCE, rID, rVal, 0));
-					break;
-				case 6: bd.components.push_back(Component (Component::PRIM_SKILL, rID, rVal, 0));
-					break;
-				case 7: bd.components.push_back(Component (Component::SEC_SKILL, rID, rVal, 0));
-					break;
-				case 8: bd.components.push_back(Component (Component::ARTIFACT, rID, 0, 0));
-					break;
-				case 9: bd.components.push_back(Component (Component::SPELL, rID, 0, 0));
-					break;
-				case 10: bd.components.push_back(Component (Component::CREATURE, rID, rVal, 0));
-					break;
-			}
+			getCompletionText (bd.text, bd.components, isCustom, h);
 			
 			cb->showBlockingDialog (&bd, boost::bind (&CGSeerHut::finishQuest, this, h, _1));
 			return;
@@ -4644,23 +4693,6 @@ const CGCreature * CGSeerHut::getCreatureToKill(bool allowNull) const
 	return static_cast<const CGCreature*>(o);
 }
 
-void CGSeerHut::addReplacements(MetaString &out, const std::string &base) const
-{
-	switch(missionType)
-	{
-	case MISSION_KILL_CREATURE:
-		out.addReplacement(stackToKill);
-		if (std::count(base.begin(), base.end(), '%') == 2) //say where is placed monster
-		{
-			out.addReplacement(VLC->generaltexth->arraytxt[147+stackDirection]);
-		}
-		break;
-	case MISSION_KILL_HERO:
-		out.addReplacement(heroName);
-		break;
-	}
-}
-
 void CGQuestGuard::initObj()
 {
 	blockVisit = true;

+ 14 - 13
lib/CObjectHandler.h

@@ -58,7 +58,7 @@ class DLL_LINKAGE CQuest
 {
 public:
 	enum Emission {MISSION_NONE = 0, MISSION_LEVEL = 1, MISSION_PRIMARY_STAT = 2, MISSION_KILL_HERO = 3, MISSION_KILL_CREATURE = 4,
-		MISSION_ART = 5, MISSION_ARMY = 6, MISSION_RESOURCES = 7, MISSION_HERO = 8, MISSION_PLAYER = 9};
+		MISSION_ART = 5, MISSION_ARMY = 6, MISSION_RESOURCES = 7, MISSION_HERO = 8, MISSION_PLAYER = 9};//MISSION_KEYMASTER = 10}; //TODO?
 
 	ui8 missionType, progress;
 	si32 lastDay; //after this day (first day is 0) mission cannot be completed; if -1 - no limit
@@ -69,15 +69,27 @@ public:
 	std::vector<CStackBasicDescriptor> m6creatures; //pair[cre id, cre count], CreatureSet info irrelevant
 	std::vector<ui32> m7resources;
 
+	//following field are used only for kill creature/hero missions, the original objects became inaccessible after their removal, so we need to store info needed for messages / hover text
+	ui8 textOption;
+	CStackBasicDescriptor stackToKill; 
+	ui8 stackDirection;
+	std::string heroName; //backup of hero name
+	si32 heroPortrait;
+
 	std::string firstVisitText, nextVisitText, completedText;
 	bool isCustomFirst, isCustomNext, isCustomComplete;
 
 	bool checkQuest (const CGHeroInstance * h) const; //determines whether the quest is complete or not
+	virtual void getVisitText (MetaString &text, std::vector<Component> &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = NULL) const;
+	virtual void getCompletionText (MetaString &text, std::vector<Component> &components, bool isCustom, const CGHeroInstance * h = NULL) const;
+	virtual void getRolloverText (MetaString &text, bool onHover) const; //hover or quest log entry
 	virtual void completeQuest (const CGHeroInstance * h) const {};
+	virtual void addReplacements(MetaString &out, const std::string &base) const;
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & missionType & progress & lastDay & m13489val & m2stats & m5arts & m6creatures & m7resources
+			& textOption & stackToKill & stackDirection & heroName & heroPortrait
 			& firstVisitText & nextVisitText & completedText & isCustomFirst & isCustomNext & isCustomComplete;
 	}
 };
@@ -754,36 +766,25 @@ public:
 	ui8 rewardType; //type of reward: 0 - no reward; 1 - experience; 2 - mana points; 3 - morale bonus; 4 - luck bonus; 5 - resources; 6 - main ability bonus (attak, defence etd.); 7 - secondary ability gain; 8 - artifact; 9 - spell; 10 - creature
 	si32 rID; //reward ID
 	si32 rVal; //reward value
-	ui8 textOption; //store randomized mission write-ups rather than entire string (?)
 	std::string seerName;
 
-	//following field are used only for kill creature/hero missions, the original objects became inaccessible after their removal, so we need to store info needed for messages / hover text
-	//TODO? organize
-	CStackBasicDescriptor stackToKill; 
-	ui8 stackDirection;
-	std::string heroName; //backup of hero name
-	si32 heroPortrait;
-
-
 	void initObj();
 	const std::string & getHoverText() const;
 	void setPropertyDer (ui8 what, ui32 val);
 	int checkDirection() const; //calculates the region of map where monster is placed
 	void newTurn() const;
 	void onHeroVisit (const CGHeroInstance * h) const;
+	void getCompletionText(MetaString &text, std::vector<Component> &components, bool isCustom, const CGHeroInstance * h = NULL) const;
 	void finishQuest (const CGHeroInstance * h, ui32 accept) const; //common for both objects
 	void completeQuest (const CGHeroInstance * h) const;
 
 	const CGHeroInstance *getHeroToKill(bool allowNull = false) const;
 	const CGCreature *getCreatureToKill(bool allowNull = false) const;
 
-	void addReplacements(MetaString &out, const std::string &base) const;
-
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & static_cast<CGObjectInstance&>(*this) & static_cast<CQuest&>(*this);
 		h & rewardType & rID & rVal & textOption & seerName;
-		h & stackToKill & stackDirection & heroName & heroPortrait;
 	}
 };