Pārlūkot izejas kodu

Merge pull request #2485 from IvanSavenko/hotfix

Hotfix for issues discovered in 1.3
Ivan Savenko 2 gadi atpakaļ
vecāks
revīzija
06b0ebf08c

+ 1 - 1
CMakePresets.json

@@ -240,7 +240,7 @@
                 "default-release"
             ],
             "cacheVariables": {
-                "CMAKE_BUILD_TYPE": "Release"
+                "CMAKE_BUILD_TYPE": "RelWithDebInfo"
             }
         }
     ],

+ 1 - 1
Mods/vcmi/mod.json

@@ -78,7 +78,7 @@
 		]
 	},
 
-	"version" : "1.2",
+	"version" : "1.3",
 	"author" : "VCMI Team",
 	"contact" : "http://forum.vcmi.eu/index.php",
 	"modType" : "Graphical",

+ 1 - 1
android/vcmi-app/build.gradle

@@ -10,7 +10,7 @@ android {
 		applicationId "is.xyz.vcmi"
 		minSdk 19
 		targetSdk 31
-		versionCode 1301
+		versionCode 1302
 		versionName "1.3.0"
 		setProperty("archivesBaseName", "vcmi")
 	}

+ 10 - 3
android/vcmi-app/src/main/java/eu/vcmi/vcmi/ActivityMods.java

@@ -182,9 +182,16 @@ public class ActivityMods extends ActivityWithToolbar
         public void onSuccess(ServerResponse<List<VCMIMod>> response)
         {
             Log.i(this, "Initialized mods repo");
-            mModContainer.updateFromRepo(response.mContent);
-            mModsAdapter.updateModsList(mModContainer.submods());
-            mProgress.setVisibility(View.GONE);
+			if (mModContainer == null)
+			{
+				handleNoData();
+			}
+			else
+			{
+				mModContainer.updateFromRepo(response.mContent);
+				mModsAdapter.updateModsList(mModContainer.submods());
+				mProgress.setVisibility(View.GONE);
+			}
         }
 
         @Override

+ 8 - 0
client/NetPacksClient.cpp

@@ -95,6 +95,14 @@ void callAllInterfaces(CClient & cl, void (T::*ptr)(Args...), Args2 && ...args)
 template<typename T, typename ... Args, typename ... Args2>
 void callBattleInterfaceIfPresentForBothSides(CClient & cl, void (T::*ptr)(Args...), Args2 && ...args)
 {
+	assert(cl.gameState()->curB);
+
+	if (!cl.gameState()->curB)
+	{
+		logGlobal->error("Attempt to call battle interface without ongoing battle!");
+		return;
+	}
+
 	callOnlyThatBattleInterface(cl, cl.gameState()->curB->sides[0].color, ptr, std::forward<Args2>(args)...);
 	callOnlyThatBattleInterface(cl, cl.gameState()->curB->sides[1].color, ptr, std::forward<Args2>(args)...);
 	if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool() && LOCPLINT->battleInt)

+ 14 - 0
client/render/Graphics.cpp

@@ -299,3 +299,17 @@ void Graphics::initializeImageLists()
 	addImageListEntries(CGI->spells());
 	addImageListEntries(CGI->skills());
 }
+
+std::shared_ptr<CAnimation> Graphics::getAnimation(const std::string & path)
+{
+	ResourceID animationPath(path, EResType::ANIMATION);
+
+	if (cachedAnimations.count(animationPath.getName()) != 0)
+		return cachedAnimations.at(animationPath.getName());
+
+	auto newAnimation = std::make_shared<CAnimation>(animationPath.getName());
+
+	newAnimation->preload();
+	cachedAnimations[animationPath.getName()] = newAnimation;
+	return newAnimation;
+}

+ 4 - 0
client/render/Graphics.h

@@ -47,7 +47,11 @@ class Graphics
 	void loadFonts();
 	void initializeImageLists();
 
+	std::map<std::string, std::shared_ptr<CAnimation>> cachedAnimations;
+
 public:
+	std::shared_ptr<CAnimation> getAnimation(const std::string & path);
+
 	//Fonts
 	static const int FONTS_NUMBER = 9;
 	std::array< std::shared_ptr<IFont>, FONTS_NUMBER> fonts;

+ 2 - 2
client/widgets/CGarrisonInt.cpp

@@ -422,10 +422,10 @@ CGarrisonSlot::CGarrisonSlot(CGarrisonInt * Owner, int x, int y, SlotID IID, EGa
 
 	std::string imgName = owner->smallIcons ? "cprsmall" : "TWCRPORT";
 
-	creatureImage = std::make_shared<CAnimImage>(imgName, 0);
+	creatureImage = std::make_shared<CAnimImage>(graphics->getAnimation(imgName), 0);
 	creatureImage->disable();
 
-	selectionImage = std::make_shared<CAnimImage>(imgName, 1);
+	selectionImage = std::make_shared<CAnimImage>(graphics->getAnimation(imgName), 1);
 	selectionImage->disable();
 
 	if(Owner->smallIcons)

+ 23 - 18
client/widgets/MiscWidgets.cpp

@@ -18,6 +18,7 @@
 #include "../CPlayerInterface.h"
 #include "../CGameInfo.h"
 #include "../widgets/TextControls.h"
+#include "../widgets/CGarrisonInt.h"
 #include "../windows/CCastleInterface.h"
 #include "../windows/InfoWindows.h"
 #include "../render/Canvas.h"
@@ -303,28 +304,30 @@ CHeroTooltip::CHeroTooltip(Point pos, const CGHeroInstance * hero):
 	init(InfoAboutHero(hero, InfoAboutHero::EInfoLevel::DETAILED));
 }
 
-CInteractableHeroTooltip::CInteractableHeroTooltip(Point pos, const CGHeroInstance * hero):
-		CGarrisonInt(pos + Point(0, 73), 4, Point(0, 0), hero, nullptr, true, true, CGarrisonInt::ESlotsLayout::REVERSED_TWO_ROWS)
+CInteractableHeroTooltip::CInteractableHeroTooltip(Point pos, const CGHeroInstance * hero)
 {
 	init(InfoAboutHero(hero, InfoAboutHero::EInfoLevel::DETAILED));
+
+	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+	garrison = std::make_shared<CGarrisonInt>(pos + Point(0, 73), 4, Point(0, 0), hero, nullptr, true, true, CGarrisonInt::ESlotsLayout::REVERSED_TWO_ROWS);
 }
 
 void CInteractableHeroTooltip::init(const InfoAboutHero & hero)
 {
 	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
-	portrait = std::make_shared<CAnimImage>("PortraitsLarge", hero.portrait, 0, 3, 2-73);
-	title = std::make_shared<CLabel>(66, 2-73, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, hero.name);
+	portrait = std::make_shared<CAnimImage>("PortraitsLarge", hero.portrait, 0, 3, 2);
+	title = std::make_shared<CLabel>(66, 2, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, hero.name);
 
 	if(hero.details)
 	{
 		for(size_t i = 0; i < hero.details->primskills.size(); i++)
-			labels.push_back(std::make_shared<CLabel>(75 + 28 * (int)i, 58-73, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE,
+			labels.push_back(std::make_shared<CLabel>(75 + 28 * (int)i, 58, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE,
 													  std::to_string(hero.details->primskills[i])));
 
-		labels.push_back(std::make_shared<CLabel>(158, 98-73, FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, std::to_string(hero.details->mana)));
+		labels.push_back(std::make_shared<CLabel>(158, 98, FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, std::to_string(hero.details->mana)));
 
-		morale = std::make_shared<CAnimImage>("IMRL22", hero.details->morale + 3, 0, 5, 74-73);
-		luck = std::make_shared<CAnimImage>("ILCK22", hero.details->luck + 3, 0, 5, 91-73);
+		morale = std::make_shared<CAnimImage>("IMRL22", hero.details->morale + 3, 0, 5, 74);
+		luck = std::make_shared<CAnimImage>("ILCK22", hero.details->luck + 3, 0, 5, 91);
 	}
 }
 
@@ -383,9 +386,11 @@ CTownTooltip::CTownTooltip(Point pos, const CGTownInstance * town)
 }
 
 CInteractableTownTooltip::CInteractableTownTooltip(Point pos, const CGTownInstance * town)
-		: CGarrisonInt(pos + Point(0, 73), 4, Point(0, 0), town->getUpperArmy(), nullptr, true, true, CGarrisonInt::ESlotsLayout::REVERSED_TWO_ROWS)
 {
 	init(InfoAboutTown(town, true));
+
+	OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
+	garrison = std::make_shared<CGarrisonInt>(pos + Point(0, 73), 4, Point(0, 0), town->getUpperArmy(), nullptr, true, true, CGarrisonInt::ESlotsLayout::REVERSED_TWO_ROWS);
 }
 
 void CInteractableTownTooltip::init(const InfoAboutTown & town)
@@ -395,37 +400,37 @@ void CInteractableTownTooltip::init(const InfoAboutTown & town)
 	//order of icons in def: fort, citadel, castle, no fort
 	size_t fortIndex = town.fortLevel ? town.fortLevel - 1 : 3;
 
-	fort = std::make_shared<CAnimImage>("ITMCLS", fortIndex, 0, 105, 31-73);
+	fort = std::make_shared<CAnimImage>("ITMCLS", fortIndex, 0, 105, 31);
 
 	assert(town.tType);
 
 	size_t iconIndex = town.tType->clientInfo.icons[town.fortLevel > 0][town.built >= CGI->settings()->getInteger(EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP)];
 
-	build = std::make_shared<CAnimImage>("itpt", iconIndex, 0, 3, 2-73);
-	title = std::make_shared<CLabel>(66, 2-73, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, town.name);
+	build = std::make_shared<CAnimImage>("itpt", iconIndex, 0, 3, 2);
+	title = std::make_shared<CLabel>(66, 2, FONT_SMALL, ETextAlignment::TOPLEFT, Colors::WHITE, town.name);
 
 	if(town.details)
 	{
-		hall = std::make_shared<CAnimImage>("ITMTLS", town.details->hallLevel, 0, 67, 31-73);
+		hall = std::make_shared<CAnimImage>("ITMTLS", town.details->hallLevel, 0, 67, 31);
 
 		if(town.details->goldIncome)
 		{
-			income = std::make_shared<CLabel>(157, 58-73, FONT_TINY, ETextAlignment::CENTER, Colors::WHITE,
+			income = std::make_shared<CLabel>(157, 58, FONT_TINY, ETextAlignment::CENTER, Colors::WHITE,
 											  std::to_string(town.details->goldIncome));
 		}
 		if(town.details->garrisonedHero) //garrisoned hero icon
-			garrisonedHero = std::make_shared<CPicture>("TOWNQKGH", 149, 76-73);
+			garrisonedHero = std::make_shared<CPicture>("TOWNQKGH", 149, 76);
 
 		if(town.details->customRes)//silo is built
 		{
 			if(town.tType->primaryRes == EGameResID::WOOD_AND_ORE )// wood & ore
 			{
-				res1 = std::make_shared<CAnimImage>("SMALRES", GameResID(EGameResID::WOOD), 0, 7, 75-73);
-				res2 = std::make_shared<CAnimImage>("SMALRES", GameResID(EGameResID::ORE), 0, 7, 88-73);
+				res1 = std::make_shared<CAnimImage>("SMALRES", GameResID(EGameResID::WOOD), 0, 7, 75);
+				res2 = std::make_shared<CAnimImage>("SMALRES", GameResID(EGameResID::ORE), 0, 7, 88);
 			}
 			else
 			{
-				res1 = std::make_shared<CAnimImage>("SMALRES", town.tType->primaryRes, 0, 7, 81-73);
+				res1 = std::make_shared<CAnimImage>("SMALRES", town.tType->primaryRes, 0, 7, 81);
 			}
 		}
 	}

+ 5 - 3
client/widgets/MiscWidgets.h

@@ -10,7 +10,6 @@
 #pragma once
 
 #include "../gui/CIntObject.h"
-#include "CGarrisonInt.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -22,6 +21,7 @@ class AFactionMember;
 VCMI_LIB_NAMESPACE_END
 
 class CLabel;
+class CGarrisonInt;
 class CCreatureAnim;
 class CComponent;
 class CAnimImage;
@@ -82,13 +82,14 @@ public:
 };
 
 /// Class for HD mod-like interactable infobox tooltip. Does not have any background!
-class CInteractableHeroTooltip : public CGarrisonInt
+class CInteractableHeroTooltip : public CIntObject
 {
 	std::shared_ptr<CLabel> title;
 	std::shared_ptr<CAnimImage> portrait;
 	std::vector<std::shared_ptr<CLabel>> labels;
 	std::shared_ptr<CAnimImage> morale;
 	std::shared_ptr<CAnimImage> luck;
+	std::shared_ptr<CGarrisonInt> garrison;
 
 	void init(const InfoAboutHero & hero);
 public:
@@ -115,7 +116,7 @@ public:
 };
 
 /// Class for HD mod-like interactable infobox tooltip. Does not have any background!
-class CInteractableTownTooltip : public CGarrisonInt
+class CInteractableTownTooltip : public CIntObject
 {
 	std::shared_ptr<CLabel> title;
 	std::shared_ptr<CAnimImage> fort;
@@ -125,6 +126,7 @@ class CInteractableTownTooltip : public CGarrisonInt
 	std::shared_ptr<CPicture> garrisonedHero;
 	std::shared_ptr<CAnimImage> res1;
 	std::shared_ptr<CAnimImage> res2;
+	std::shared_ptr<CGarrisonInt> garrison;
 
 	void init(const InfoAboutTown & town);
 public:

+ 0 - 2
config/schemas/settings.json

@@ -201,8 +201,6 @@
 				},
 				"targetfps" : {
 					"type" : "number",
-					"defaultIOS" : 30, // reduce battery usage
-					"defaultAndroid" : 30, // reduce battery usage
 					"default" : 60
 				}
 			}

+ 1 - 1
lib/CHeroHandler.cpp

@@ -770,7 +770,7 @@ std::vector<bool> CHeroHandler::getDefaultAllowed() const
 
 	for(const CHero * hero : objects)
 	{
-		allowedHeroes.push_back(!hero->special);
+		allowedHeroes.push_back(hero && !hero->special);
 	}
 
 	return allowedHeroes;

+ 7 - 1
lib/JsonNode.cpp

@@ -829,7 +829,13 @@ std::shared_ptr<Bonus> JsonUtils::parseBonus(const JsonNode &ability)
 	auto b = std::make_shared<Bonus>();
 	if (!parseBonus(ability, b.get()))
 	{
-		return nullptr;
+		// caller code can not handle this case and presumes that returned bonus is always valid
+		logGlobal->error("Failed to parse bonus! Json config was %S ", ability.toJson());
+
+		b->type = BonusType::NONE;
+		assert(0); // or throw? Game *should* work with dummy bonus
+
+		return b;
 	}
 	return b;
 }

+ 5 - 5
server/NetPacksServer.cpp

@@ -282,24 +282,24 @@ void ApplyGhNetPackVisitor::visitMakeAction(MakeAction & pack)
 {
 	const BattleInfo * b = gs.curB;
 	if(!b)
-		gh.throwNotAllowedAction(&pack);
+		gh.throwAndComplain(&pack, "Can not make action - there is no battle ongoing!");
 
 	if(b->tacticDistance)
 	{
 		if(pack.ba.actionType != EActionType::WALK && pack.ba.actionType != EActionType::END_TACTIC_PHASE
 			&& pack.ba.actionType != EActionType::RETREAT && pack.ba.actionType != EActionType::SURRENDER)
-			gh.throwNotAllowedAction(&pack);
+			gh.throwAndComplain(&pack, "Can not make actions while in tactics mode!");
 		if(!vstd::contains(gh.connections[b->sides[b->tacticsSide].color], pack.c))
-			gh.throwNotAllowedAction(&pack);
+			gh.throwAndComplain(&pack, "Can not make actions in battles you are not part of!");
 	}
 	else
 	{
 		auto active = b->battleActiveUnit();
 		if(!active)
-			gh.throwNotAllowedAction(&pack);
+			gh.throwAndComplain(&pack, "No active unit in battle!");
 		auto unitOwner = b->battleGetOwner(active);
 		if(!vstd::contains(gh.connections[unitOwner], pack.c))
-			gh.throwNotAllowedAction(&pack);
+			gh.throwAndComplain(&pack, "Can not make actions in battles you are not part of!");
 	}
 
 	result = gh.makeBattleAction(pack.ba);