Преглед изворни кода

Merge pull request #5947 from Laserlicht/skill

Setting to increase selectable skills on levelup
Ivan Savenko пре 2 месеци
родитељ
комит
c0644da408

+ 70 - 12
client/windows/GUIClasses.cpp

@@ -404,23 +404,39 @@ void CSplitWindow::sliderMoved(int to)
 
 CLevelWindow::CLevelWindow(const CGHeroInstance * hero, PrimarySkill pskill, std::vector<SecondarySkill> & skills, std::function<void(ui32)> callback)
 	: CWindowObject(PLAYER_COLORED, ImagePath::builtin("LVLUPBKG")),
-	cb(callback)
+	cb(callback),
+	skills(skills),
+	hero(hero),
+	skillViewOffset(0)
 {
 	OBJECT_CONSTRUCTION;
 
 	GAME->interface()->showingDialog->setBusy();
 
-	if(!skills.empty())
-	{
-		std::vector<std::shared_ptr<CSelectableComponent>> comps;
-		for(auto & skill : skills)
-		{
-			auto comp = std::make_shared<CSelectableComponent>(ComponentType::SEC_SKILL, skill, hero->getSecSkillLevel(SecondarySkill(skill))+1, CComponent::medium);
-			comp->onChoose = std::bind(&CLevelWindow::close, this);
-			comps.push_back(comp);
-		}
+	sortedSkills = skills;
+	std::sort(sortedSkills.begin(), sortedSkills.end(), [hero](auto a, auto b) {
+		if(hero->getSecSkillLevel(a) == hero->getSecSkillLevel(b))
+			return LIBRARY->skillh->getById(a)->getNameTranslated() < LIBRARY->skillh->getById(b)->getNameTranslated();
+		return hero->getSecSkillLevel(a) > hero->getSecSkillLevel(b);
+	});
 
-		box = std::make_shared<CComponentBox>(comps, Rect(75, 300, pos.w - 150, 100));
+	createSkillBox();
+	if(skills.size() > 3)
+	{
+		buttonLeft = std::make_shared<CButton>(Point(23, 309), AnimationPath::builtin("HSBTNS3"), CButton::tooltip(), [this, skills](){
+			if(skillViewOffset > 0)
+				skillViewOffset--;
+			else
+				skillViewOffset = skills.size() - 1;
+			createSkillBox();
+		}, EShortcut::MOVE_LEFT);
+		buttonRight = std::make_shared<CButton>(Point(pos.w - 45, 309), AnimationPath::builtin("HSBTNS5"), CButton::tooltip(), [this, skills](){
+			if(skillViewOffset < skills.size() - 1)
+				skillViewOffset++;
+			else
+				skillViewOffset = 0;
+			createSkillBox();
+		}, EShortcut::MOVE_RIGHT);
 	}
 
 	portrait = std::make_shared<CHeroArea>(170, 66, hero);
@@ -444,11 +460,53 @@ CLevelWindow::CLevelWindow(const CGHeroInstance * hero, PrimarySkill pskill, std
 	skillValue = std::make_shared<CLabel>(192, 253, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, LIBRARY->generaltexth->primarySkillNames[pskill.getNum()] + " +1");
 }
 
+std::vector<SecondarySkill> getSkillsToShow(const std::vector<SecondarySkill>& skills, int offset, int count)
+{
+	std::vector<SecondarySkill> result;
+
+	int size = skills.size();
+	if (size == 0 || count <= 0) return result;
+
+	offset = offset % size; // ensure offset is within bounds
+	for (int i = 0; i < std::min(count, size); ++i)
+	{
+		int index = (offset + i) % size; // ring buffer like
+		result.push_back(skills[index]);
+	}
+
+	return result;
+}
+
+void CLevelWindow::createSkillBox()
+{
+	OBJECT_CONSTRUCTION;
+
+	std::vector<SecondarySkill> skillsToShow = skills.size() > 3 ? getSkillsToShow(sortedSkills, skillViewOffset, 3) : sortedSkills;
+	if(!skillsToShow.empty())
+	{
+		std::vector<std::shared_ptr<CSelectableComponent>> comps;
+		for(auto & skill : skillsToShow)
+		{
+			auto comp = std::make_shared<CSelectableComponent>(ComponentType::SEC_SKILL, skill, hero->getSecSkillLevel(SecondarySkill(skill))+1, CComponent::medium);
+			comp->onChoose = std::bind(&CLevelWindow::close, this);
+			comps.push_back(comp);
+		}
+
+		box = std::make_shared<CComponentBox>(comps, Rect(75, 300, pos.w - 150, 100));
+	}
+
+	setRedrawParent(true);
+	redraw();
+}
+
 void CLevelWindow::close()
 {
 	//FIXME: call callback if there was nothing to select?
 	if (box && box->selectedIndex() != -1)
-		cb(box->selectedIndex());
+	{
+		auto it = std::find(skills.begin(), skills.end(), sortedSkills[(box->selectedIndex() + skillViewOffset) % skills.size()]);
+		cb(std::distance(skills.begin(), it));
+	}
 
 	GAME->interface()->showingDialog->setFree();
 

+ 9 - 0
client/windows/GUIClasses.h

@@ -152,7 +152,16 @@ class CLevelWindow : public CWindowObject
 	std::shared_ptr<CComponentBox> box; //skills to select
 	std::function<void(ui32)> cb;
 
+	int skillViewOffset = 0;
+	std::shared_ptr<CButton> buttonLeft;
+	std::shared_ptr<CButton> buttonRight;
+
+	std::vector<SecondarySkill> skills;
+	std::vector<SecondarySkill> sortedSkills;
+	const CGHeroInstance * hero;
+
 	void selectionChanged(unsigned to);
+	void createSkillBox();
 
 public:
 	CLevelWindow(const CGHeroInstance *hero, PrimarySkill pskill, std::vector<SecondarySkill> &skills, std::function<void(ui32)> callback);

+ 6 - 1
config/gameConfig.json

@@ -527,7 +527,12 @@
 			/// Strength of generic secondary skill specialties ( "secondary" : "skillName" ) per level
 			"specialtySecondarySkillGrowth" : 5,
 			/// Strength of generic creature specialties ( "creature" : "creatureName" ) per level
-			"specialtyCreatureGrowth" : 5
+			"specialtyCreatureGrowth" : 5,
+
+			/// Maximum amount of skills that can be offered to hero on levelup
+			"levelupTotalSkillsAmount" : 2,
+			/// Maximum amount of advanced or expert skills that can be offered to hero on levelup
+			"levelupUpgradedSkillsAmount" : 1
 		},
 
 		"towns":

+ 15 - 13
config/schemas/gameSettings.json

@@ -38,20 +38,22 @@
 			"type" : "object",
 			"additionalProperties" : false,
 			"properties" : {
-				"perPlayerOnMapCap"  :        { "type" : "number" },
-				"perPlayerTotalCap"  :        { "type" : "number" },
-				"retreatOnWinWithoutTroops" : { "type" : "boolean" },
-				"startingStackChances" :      { "type" : "array" },
-				"backpackSize" :              { "type" : "number" },
-				"tavernInvite" :              { "type" : "boolean" },
-				"minimalPrimarySkills" :      { "type" : "array" },
-				"movementCostBase"  :         { "type" : "number" },
-				"movementPointsLand" :        { "type" : "array" },
-				"movementPointsSea" :         { "type" : "array" },
-				"skillPerHero" :              { "type" : "number" },
-				"specialtyCreatureGrowth" :    { "type" : "number" },
+				"perPlayerOnMapCap"  :            { "type" : "number" },
+				"perPlayerTotalCap"  :            { "type" : "number" },
+				"retreatOnWinWithoutTroops" :     { "type" : "boolean" },
+				"startingStackChances" :          { "type" : "array" },
+				"backpackSize" :                  { "type" : "number" },
+				"tavernInvite" :                  { "type" : "boolean" },
+				"minimalPrimarySkills" :          { "type" : "array" },
+				"movementCostBase"  :             { "type" : "number" },
+				"movementPointsLand" :            { "type" : "array" },
+				"movementPointsSea" :             { "type" : "array" },
+				"skillPerHero" :                  { "type" : "number" },
+				"specialtyCreatureGrowth" :       { "type" : "number" },
 				"specialtySecondarySkillGrowth" : { "type" : "number" },
-				"baseScoutingRange" :         { "type" : "number" }
+				"baseScoutingRange" :             { "type" : "number" },
+				"levelupTotalSkillsAmount" :      { "type" : "number" },
+				"levelupUpgradedSkillsAmount" :   { "type" : "number" }
 			}
 		},
 		"towns" : {

+ 2 - 0
lib/GameSettings.cpp

@@ -86,6 +86,8 @@ const std::vector<GameSettings::SettingOption> GameSettings::settingProperties =
 		{EGameSettings::HEROES_SKILL_PER_HERO,                            "heroes",    "skillPerHero"                         },
 		{EGameSettings::HEROES_SPECIALTY_CREATURE_GROWTH,                 "heroes",    "specialtyCreatureGrowth"              },
 		{EGameSettings::HEROES_SPECIALTY_SECONDARY_SKILL_GROWTH,          "heroes",    "specialtySecondarySkillGrowth"        },
+		{EGameSettings::LEVEL_UP_TOTAL_SKILLS_AMOUNT,                     "heroes",    "levelupTotalSkillsAmount"             },
+		{EGameSettings::LEVEL_UP_UPGRADED_SKILLS_AMOUNT,                  "heroes",    "levelupUpgradedSkillsAmount"          },
 		{EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE,                     "mapFormat", "armageddonsBlade"                     },
 		{EGameSettings::MAP_FORMAT_CHRONICLES,                            "mapFormat", "chronicles"                           },
 		{EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS,                     "mapFormat", "hornOfTheAbyss"                       },

+ 2 - 0
lib/IGameSettings.h

@@ -59,6 +59,8 @@ enum class EGameSettings
 	HEROES_SKILL_PER_HERO,
 	HEROES_SPECIALTY_CREATURE_GROWTH,
 	HEROES_SPECIALTY_SECONDARY_SKILL_GROWTH,
+	LEVEL_UP_TOTAL_SKILLS_AMOUNT,
+	LEVEL_UP_UPGRADED_SKILLS_AMOUNT,
 	INTERFACE_PLAYER_COLORED_BACKGROUND,
 	MAP_FORMAT_ARMAGEDDONS_BLADE,
 	MAP_FORMAT_CHRONICLES,

+ 5 - 15
lib/mapObjects/CGHeroInstance.cpp

@@ -1422,26 +1422,16 @@ std::vector<SecondarySkill> CGHeroInstance::getLevelupSkillCandidates(IGameRando
 			basicAndAdv.insert(elem.first);
 		none.erase(elem.first);
 	}
-
-	if (!basicAndAdv.empty())
-	{
-		skills.push_back(gameRandomizer.rollSecondarySkillForLevelup(this, basicAndAdv));
-		basicAndAdv.erase(skills.back());
-	}
-
-	if (!none.empty())
-	{
-		skills.push_back(gameRandomizer.rollSecondarySkillForLevelup(this, none));
-		none.erase(skills.back());
-	}
-
-	if (!basicAndAdv.empty() && skills.size() < 2)
+	
+	int maxUpgradedSkills = cb->getSettings().getInteger(EGameSettings::LEVEL_UP_UPGRADED_SKILLS_AMOUNT);
+	while (skills.size() < maxUpgradedSkills && !basicAndAdv.empty())
 	{
 		skills.push_back(gameRandomizer.rollSecondarySkillForLevelup(this, basicAndAdv));
 		basicAndAdv.erase(skills.back());
 	}
 
-	if (!none.empty() && skills.size() < 2)
+	int maxTotalSkills = cb->getSettings().getInteger(EGameSettings::LEVEL_UP_TOTAL_SKILLS_AMOUNT);
+	while (skills.size() < maxTotalSkills && !none.empty())
 	{
 		skills.push_back(gameRandomizer.rollSecondarySkillForLevelup(this, none));
 		none.erase(skills.back());