Browse Source

Merge pull request #6276 from IvanSavenko/misc_fixes

Fixes for multiple recently reported issues
Ivan Savenko 1 month ago
parent
commit
dcb086d1da

+ 1 - 0
Mods/vcmi/Content/config/english.json

@@ -1016,6 +1016,7 @@
 	"core.bonus.MANA_DRAIN.description" : "{Drains enemy mana}\nDrains ${val} mana every turn from enemy hero",
 	"core.bonus.MECHANICAL.description" : "{Mechanical}\nThis unit is immune to effects that only affect living and can be repaired",
 	"core.bonus.MIND_IMMUNITY.description" : "{Mind Spell Immunity}\nThis unit cannot be targeted by spells that affect its mind",
+	"core.bonus.MORE_DAMAGE_FROM_SPELL.description" : "{Vulnerable to ${subtype.spell}}\nThe damage taken by this unit when hit by a ${subtype.spell} is increased by ${val}%",
 	"core.bonus.NO_DISTANCE_PENALTY.description" : "{No distance penalty}\nRanged attacks deal full damage at any distance",
 	"core.bonus.NO_MELEE_PENALTY.description" : "{No melee penalty}\nThis ranged unit deals full damage with melee attacks",
 	"core.bonus.NO_MORALE.description" : "{Neutral Morale}\nCreature is immune to morale effects",

+ 1 - 1
client/battle/BattleProjectileController.cpp

@@ -161,7 +161,7 @@ const CCreature & BattleProjectileController::getShooter(const CStack * stack) c
 	if(creature->getId() == CreatureID::ARROW_TOWERS)
 		creature = owner.siegeController->getTurretCreature(stack->initialPosition);
 
-	if(creature->animation.missileFrameAngles.empty())
+	if(creature->animation.missileFrameAngles.empty() && creature->animation.projectileRay.empty())
 	{
 		logAnim->error("Mod error: Creature '%s' on the Archer's tower is not a shooter. Mod should be fixed. Trying to use archer's data instead...", creature->getNameSingularTranslated());
 		creature = CreatureID(CreatureID::ARCHER).toCreature();

+ 25 - 1
client/windows/CCastleInterface.cpp

@@ -2184,8 +2184,16 @@ void CMageGuildScreen::updateSpells(ObjectInstanceID tID)
 		uint32_t spellCount = town->spellsAtLevel(i+1,false); //spell at level with -1 hmmm?
 		for(uint32_t j=0; j<spellCount; j++)
 		{
-			if(i<town->mageGuildLevel() && town->spells[i].size()>j)
+			if (town->hasBuilt(BuildingSubID::AURORA_BOREALIS))
+			{
+				std::string auroraBorealisName = town->getTown()->getSpecialBuilding(BuildingSubID::AURORA_BOREALIS)->getNameTranslated();
+
+				auroraBorealisScrolls.push_back(std::make_shared<ScrollAllSpells>(positions[i][j], auroraBorealisName));
+			}
+			else if(i<town->mageGuildLevel() && town->spells[i].size()>j)
+			{
 				spells.push_back(std::make_shared<Scroll>(positions[i][j], town->spells[i][j].toSpell(), townId));
+			}
 			else
 				emptyScrolls.push_back(std::make_shared<CAnimImage>(AnimationPath::builtin("TPMAGES.DEF"), 1, 0, positions[i][j].x, positions[i][j].y));
 		}
@@ -2194,6 +2202,22 @@ void CMageGuildScreen::updateSpells(ObjectInstanceID tID)
 	redraw();
 }
 
+CMageGuildScreen::ScrollAllSpells::ScrollAllSpells(Point position, const std::string & buildingName)
+{
+	constexpr int auroraBorealisImageIndex = 70;
+
+	OBJECT_CONSTRUCTION;
+	pos += position;
+	image = std::make_shared<CAnimImage>(AnimationPath::builtin("SPELLSCR"), auroraBorealisImageIndex);
+	pos = image->pos;
+
+	MetaString description;
+	description.appendTextID("core.genrltxt.714");
+	description.replaceRawString(buildingName);
+
+	text = std::make_shared<LRClickableAreaWText>(Rect(Point(), pos.dimensions()), description.toString(), description.toString() );
+}
+
 CMageGuildScreen::Scroll::Scroll(Point position, const CSpell *Spell, ObjectInstanceID townId)
 	: spell(Spell), townId(townId)
 {

+ 12 - 0
client/windows/CCastleInterface.h

@@ -37,6 +37,7 @@ class CGarrisonInt;
 class CComponent;
 class CComponentBox;
 class LRClickableArea;
+class LRClickableAreaWText;
 class CTextInputWithConfirm;
 
 /// Building "button"
@@ -394,10 +395,21 @@ class CMageGuildScreen : public CStatusbarWindow
 		void showPopupWindow(const Point & cursorPosition) override;
 		void hover(bool on) override;
 	};
+
+	class ScrollAllSpells : public CIntObject
+	{
+		std::shared_ptr<CAnimImage> image;
+		std::shared_ptr<LRClickableAreaWText> text;
+
+	public:
+		ScrollAllSpells(Point position, const std::string & buildingName);
+	};
+
 	std::shared_ptr<CPicture> window;
 	std::shared_ptr<CButton> exit;
 	std::vector<std::shared_ptr<Scroll>> spells;
 	std::vector<std::shared_ptr<CAnimImage>> emptyScrolls;
+	std::vector<std::shared_ptr<ScrollAllSpells>> auroraBorealisScrolls;
 
 	std::shared_ptr<CMinorResDataBar> resdatabar;
 

+ 1 - 1
config/factions/conflux.json

@@ -196,7 +196,7 @@
 						"earthMagic"
 					]
 				},
-				"grail":          { },
+				"grail":          { "type" : "auroraBorealis" },
 				"extraTownHall":  { },
 				"extraCityHall":  { },
 				"extraCapitol":   { },

+ 1 - 1
config/factions/inferno.json

@@ -205,7 +205,7 @@
 				},
 				"horde2":         { "upgrades" : "dwellingLvl3" },
 				"horde2Upgr":     { "upgrades" : "dwellingUpLvl3", "requires" : [ "horde2" ] },
-				"grail":          { },
+				"grail":          { "type" : "deityOfFire" },
 
 				"dwellingLvl1":   { "requires" : [ "fort" ] },
 				"dwellingLvl2":   { "requires" : [ "dwellingLvl1" ] },

+ 1 - 1
config/schemas/townBuilding.json

@@ -36,7 +36,7 @@
 		},
 		"type" : {
 			"type" : "string",
-			"enum" : [ "mysticPond", "castleGate", "portalOfSummoning", "library", "escapeTunnel", "treasury", "bank" ],
+			"enum" : [ "mysticPond", "castleGate", "portalOfSummoning", "library", "escapeTunnel", "treasury", "bank", "auroraBorealis", "deityOfFire" ],
 			"description" : "Subtype for some special buildings"
 		},
 		"mode" : {

+ 18 - 1
docs/modders/Bonus/Bonus_Types.md

@@ -543,9 +543,26 @@ When affected unit is attacked from behind, it will receive more damage when att
 
 Affected unit will deal more damage when attacking specific creature
 
-- subtype - identifier of hated creature, ie. "creature.genie"
+- subtype - identifier of hated creature, ie. `genie`
 - val - additional damage, percentage
 
+### HATES_TRAIT
+
+Affected unit will deal more damage when attacking unit that has specific bonus. Note that this bonus has no assigned description. To make it visible in creature window UI, make sure to provide custom description for such bonus.
+
+- subtype - identifier of hated bonus, ie. `UNDEAD`
+- val - additional damage, percentage
+
+Example: Unit deals 50% more damage to any target that has UNDEAD bonus
+
+```json
+	"hatesUndead" : {
+		"type" : "HATES_TRAIT",
+		"subtype" : "UNDEAD",
+		"val" : 50
+	}
+```
+
 ### SPELL_LIKE_ATTACK
 
 Affected unit ranged attack will use animation and range of specified spell (Magog, Lich)

+ 2 - 4
docs/modders/Entities_Format/Town_Building_Format.md

@@ -238,15 +238,13 @@ Building requirements can be described using logical expressions:
 Following Heroes III buildings can be used as unique buildings for a town. Their functionality should be identical to a corresponding H3 building. H3 buildings that are not present in this list contain no hardcoded functionality. See vcmi json configuration to see how such buildings can be implemented in a mod.
 
 - `mysticPond`
-- `artifactMerchant`
-- `freelancersGuild`
-- `magicUniversity`
 - `castleGate`
-- `creatureTransformer`
 - `portalOfSummoning`
 - `library`
 - `escapeTunnel`
 - `treasury`
+- `auroraBorealis`
+- `deityOfFire`
 
 #### Buildings from other Heroes III mods
 

+ 16 - 2
lib/battle/DamageCalculator.cpp

@@ -285,13 +285,26 @@ double DamageCalculator::getAttackFromBackFactor() const
 	return 0;
 }
 
-double DamageCalculator::getAttackHateFactor() const
+double DamageCalculator::getAttackHateCreatureFactor() const
 {
 	//assume that unit have only few HATE features and cache them all
 	auto allHateEffects = info.attacker->getBonusesOfType(BonusType::HATE);
 	return allHateEffects->valOfBonuses(Selector::subtype()(BonusSubtypeID(info.defender->creatureId()))) / 100.0;
 }
 
+double DamageCalculator::getAttackHateTraitFactor() const
+{
+	//assume that unit have only few HATE features and cache them all
+	auto allHateEffects = info.attacker->getBonusesOfType(BonusType::HATES_TRAIT);
+
+	auto selector = [this](const Bonus* hateBonus) -> bool
+	{
+		return info.defender->hasBonusOfType(hateBonus->subtype.as<BonusTypeID>().toEnum());
+	};
+
+	return allHateEffects->valOfBonuses(selector) / 100.0;
+}
+
 double DamageCalculator::getAttackRevengeFactor() const
 {
 	if(info.attacker->hasBonusOfType(BonusType::REVENGE)) //HotA Haspid ability
@@ -475,7 +488,8 @@ std::vector<double> DamageCalculator::getAttackFactors() const
 		getAttackFromBackFactor(),
 		getAttackDeathBlowFactor(),
 		getAttackDoubleDamageFactor(),
-		getAttackHateFactor(),
+		getAttackHateCreatureFactor(),
+		getAttackHateTraitFactor(),
 		getAttackRevengeFactor()
 	};
 }

+ 2 - 1
lib/battle/DamageCalculator.h

@@ -50,7 +50,8 @@ class DLL_LINKAGE DamageCalculator
 	double getAttackJoustingFactor() const;
 	double getAttackDeathBlowFactor() const;
 	double getAttackDoubleDamageFactor() const;
-	double getAttackHateFactor() const;
+	double getAttackHateCreatureFactor() const;
+	double getAttackHateTraitFactor() const;
 	double getAttackRevengeFactor() const;
 	double getAttackFromBackFactor() const;
 

+ 17 - 0
lib/bonuses/BonusCustomTypes.cpp

@@ -10,6 +10,8 @@
 
 #include "StdInc.h"
 #include "BonusCustomTypes.h"
+#include "CBonusTypeHandler.h"
+#include "GameLibrary.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -75,4 +77,19 @@ std::string BonusCustomSource::encode(const si32 index)
 	return std::to_string(index);
 }
 
+std::string BonusTypeID::encode(int32_t index)
+{
+	if (index == static_cast<int32_t>(BonusType::NONE))
+		return "";
+	return LIBRARY->bth->bonusToString(static_cast<BonusType>(index));
+}
+
+si32 BonusTypeID::decode(const std::string & identifier)
+{
+	if (identifier.empty())
+		return RiverId::NO_RIVER.getNum();
+
+	return resolveIdentifier("bonus", identifier);
+}
+
 VCMI_LIB_NAMESPACE_END

+ 23 - 2
lib/bonuses/BonusCustomTypes.h

@@ -11,6 +11,7 @@
 
 #include "../constants/EntityIdentifiers.h"
 #include "../constants/VariantIdentifier.h"
+#include "BonusEnum.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
@@ -77,7 +78,27 @@ public:
 	static BonusCustomSubtype creatureLevel(int level);
 };
 
-using BonusSubtypeID = VariantIdentifier<BonusCustomSubtype, SpellID, CreatureID, PrimarySkill, TerrainId, GameResID, SpellSchool>;
-using BonusSourceID = VariantIdentifier<BonusCustomSource, SpellID, CreatureID, ArtifactID, ArtifactInstanceID, CampaignScenarioID, SecondarySkill, HeroTypeID, Obj, ObjectInstanceID, BuildingTypeUniqueID, BattleField>;
+class DLL_LINKAGE BonusTypeID : public EntityIdentifier<BonusTypeID>
+{
+public:
+	using EntityIdentifier<BonusTypeID>::EntityIdentifier;
+	using EnumType = BonusType;
+
+	static std::string encode(int32_t index);
+	static si32 decode(const std::string & identifier);
+
+	constexpr EnumType toEnum() const
+	{
+		return static_cast<EnumType>(EntityIdentifier::num);
+	}
+
+	constexpr BonusTypeID(const EnumType & enumValue)
+	{
+		EntityIdentifier::num = static_cast<int32_t>(enumValue);
+	}
+};
+
+using BonusSubtypeID = VariantIdentifier<BonusCustomSubtype, SpellID, CreatureID, PrimarySkill, TerrainId, GameResID, SpellSchool, BonusTypeID>;
+using BonusSourceID = VariantIdentifier<BonusCustomSource, SpellID, CreatureID, ArtifactID, CampaignScenarioID, SecondarySkill, HeroTypeID, Obj, ObjectInstanceID, BuildingTypeUniqueID, BattleField, ArtifactInstanceID>;
 
 VCMI_LIB_NAMESPACE_END

+ 1 - 0
lib/bonuses/BonusEnum.h

@@ -194,6 +194,7 @@ class JsonNode;
 	BONUS_NAME(TRANSMUTATION_IMMUNITY) /*blocks TRANSMUTATION bonus*/\
 	BONUS_NAME(COMBAT_MANA_BONUS) /* Additional mana per combat */ \
 	BONUS_NAME(SPECIFIC_SPELL_RANGE) /* value used for allowed spell range, subtype - spell id */\
+	BONUS_NAME(HATES_TRAIT) /* affected unit deals additional damage to units with specific bonus. subtype - bonus, val - damage bonus percent */ \
 	/* end of list */
 
 

+ 3 - 1
lib/constants/Enumerations.h

@@ -31,7 +31,9 @@ namespace BuildingSubID
 		PORTAL_OF_SUMMONING,
 		ESCAPE_TUNNEL,
 		TREASURY,
-		BANK
+		BANK,
+		AURORA_BOREALIS,
+		DEITY_OF_FIRE
 	};
 }
 

+ 3 - 1
lib/constants/StringConstants.h

@@ -135,7 +135,9 @@ namespace MappedKeys
 		{ "library", BuildingSubID::LIBRARY },
 		{ "escapeTunnel", BuildingSubID::ESCAPE_TUNNEL },
 		{ "treasury", BuildingSubID::TREASURY },
-		{ "bank", BuildingSubID::BANK }
+		{ "bank", BuildingSubID::BANK },
+		{ "auroraBorealis", BuildingSubID::AURORA_BOREALIS },
+		{ "deityOfFire", BuildingSubID::DEITY_OF_FIRE }
 	};
 
 	static const std::map<std::string, EMarketMode> MARKET_NAMES_TO_TYPES =

+ 16 - 12
lib/gameState/CGameState.cpp

@@ -1242,17 +1242,20 @@ bool CGameState::checkForVictory(const PlayerColor & player, const EventConditio
 		case EventCondition::HAVE_CREATURES:
 		{
 			//check if in players armies there is enough creatures
-			int total = 0; //creature counter
-			for(auto ai : map->getObjects<CArmedInstance>())
-			{
-				if(ai->getOwner() == player)
-				{
-					for(const auto & elem : ai->Slots()) //iterate through army
-						if(elem.second->getId() == condition.objectType.as<CreatureID>()) //it's searched creature
-							total += elem.second->getCount();
-				}
-			}
-			return total >= condition.value;
+			// NOTE: only heroes & towns are checked, in line with H3.
+			// Garrisons, mines, and guards of owned dwellings(!) are excluded
+			int totalCreatures = 0;
+			for (const auto & hero : p->getHeroes())
+				for(const auto & elem : hero->Slots()) //iterate through army
+					if(elem.second->getId() == condition.objectType.as<CreatureID>()) //it's searched creature
+						totalCreatures += elem.second->getCount();
+
+			for (const auto & town : p->getTowns())
+				for(const auto & elem : town->Slots()) //iterate through army
+					if(elem.second->getId() == condition.objectType.as<CreatureID>()) //it's searched creature
+						totalCreatures += elem.second->getCount();
+
+			return totalCreatures >= condition.value;
 		}
 		case EventCondition::HAVE_RESOURCES:
 		{
@@ -1626,18 +1629,19 @@ void CGameState::loadGame(CLoadFile & file)
 	logGlobal->info("Loading game state...");
 
 	CMapHeader dummyHeader;
-	StartInfo dummyStartInfo;
 	ActiveModsInSaveList dummyActiveMods;
 
 	file.load(dummyHeader);
 	if (file.hasFeature(ESerializationVersion::NO_RAW_POINTERS_IN_SERIALIZER))
 	{
+		StartInfo dummyStartInfo;
 		file.load(dummyStartInfo);
 		file.load(dummyActiveMods);
 		file.load(*this);
 	}
 	else
 	{
+		auto dummyStartInfo = std::make_shared<StartInfo>();
 		bool dummyA = false;
 		uint32_t dummyB = 0;
 		uint16_t dummyC = 0;

+ 0 - 2
lib/gameState/CGameStateCampaign.h

@@ -81,11 +81,9 @@ public:
 		{
 			bool dummyA = false;
 			uint32_t dummyB = 0;
-			uint16_t dummyC = 0;
 
 			h & dummyA;
 			h & dummyB;
-			h & dummyC;
 		}
 	}
 };

+ 8 - 0
lib/json/JsonBonus.cpp

@@ -91,6 +91,14 @@ static void loadBonusSubtype(BonusSubtypeID & subtype, BonusType type, const Jso
 			});
 			break;
 		}
+		case BonusType::HATES_TRAIT:
+		{
+			LIBRARY->identifiers()->requestIdentifier( "bonus", node, [&subtype](int32_t identifier)
+			{
+				subtype = BonusType(identifier);
+			});
+			break;
+		}
 		case BonusType::NO_TERRAIN_PENALTY:
 		{
 			LIBRARY->identifiers()->requestIdentifier( "terrain", node, [&subtype](int32_t identifier)

+ 0 - 7
lib/mapObjects/CGTownInstance.cpp

@@ -905,13 +905,6 @@ bool CGTownInstance::hasBuilt(const BuildingID & buildingID) const
 	return vstd::contains(builtBuildings, buildingID);
 }
 
-bool CGTownInstance::hasBuilt(const BuildingID & buildingID, FactionID townID) const
-{
-	if (townID == getTown()->faction->getId() || townID == FactionID::ANY)
-		return hasBuilt(buildingID);
-	return false;
-}
-
 void CGTownInstance::addBuilding(const BuildingID & buildingID)
 {
 	if(buildingID == BuildingID::NONE)

+ 0 - 1
lib/mapObjects/CGTownInstance.h

@@ -165,7 +165,6 @@ public:
 	bool hasBuilt(BuildingSubID::EBuildingSubID buildingID) const;
 	//checks if building is constructed and town has same subID
 	bool hasBuilt(const BuildingID & buildingID) const;
-	bool hasBuilt(const BuildingID & buildingID, FactionID townID) const;
 	void addBuilding(const BuildingID & buildingID);
 	void removeBuilding(const BuildingID & buildingID);
 	void removeAllBuildings();

+ 9 - 1
lib/rewardable/Reward.h

@@ -143,12 +143,20 @@ struct DLL_LINKAGE Reward final
 		h & movePoints;
 		h & primary;
 		h & secondary;
-		h & heroBonuses;
 		if (h.version >= Handler::Version::REWARDABLE_EXTENSIONS)
 		{
+			h & heroBonuses;
 			h & playerBonuses;
 			h & commanderBonuses;
 		}
+		else
+		{
+			std::vector<Bonus> bonuses;
+			h & bonuses;
+			for (const auto & bonus : bonuses)
+				heroBonuses.push_back(std::make_shared<Bonus>(bonus));
+		}
+
 		h & grantedArtifacts;
 		if (h.version >= Handler::Version::REWARDABLE_EXTENSIONS)
 		{

+ 1 - 1
lib/spells/CSpellHandler.cpp

@@ -953,7 +953,7 @@ std::shared_ptr<CSpell> CSpellHandler::loadFromJson(const std::string & scope, c
 
 		const si32 levelPower     = levelObject.power = static_cast<si32>(levelNode["power"].Integer());
 
-		if (!spell->isCreatureAbility())
+		if (!levelNode["description"].String().empty())
 			LIBRARY->generaltexth->registerString(scope, spell->getDescriptionTextID(levelIndex), levelNode["description"]);
 
 		levelObject.cost          = static_cast<si32>(levelNode["cost"].Integer());

+ 15 - 19
server/CGameHandler.cpp

@@ -773,7 +773,7 @@ void CGameHandler::giveSpells(const CGTownInstance *t, const CGHeroInstance *h)
 	ChangeSpells cs;
 	cs.hid = h->id;
 	cs.learn = true;
-	if (t->hasBuilt(BuildingID::GRAIL, ETownType::CONFLUX) && t->hasBuilt(BuildingID::MAGES_GUILD_1))
+	if (t->hasBuilt(BuildingSubID::AURORA_BOREALIS) && t->hasBuilt(BuildingID::MAGES_GUILD_1))
 	{
 		// Aurora Borealis give spells of all levels even if only level 1 mages guild built
 		for (int i = 0; i < h->maxSpellLevel(); i++)
@@ -2115,22 +2115,6 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID,
 		}
 	};
 
-	//Performs stuff that has to be done after new building is built
-	auto processAfterBuiltStructure = [t, this](const BuildingID buildingID)
-	{
-		auto isMageGuild = (buildingID <= BuildingID::MAGES_GUILD_5 && buildingID >= BuildingID::MAGES_GUILD_1);
-		auto isLibrary = isMageGuild ? false
-			: t->getTown()->buildings.at(buildingID)->subId == BuildingSubID::EBuildingSubID::LIBRARY;
-
-		if(isMageGuild || isLibrary || (t->getFactionID() == ETownType::CONFLUX && buildingID == BuildingID::GRAIL))
-		{
-			if(t->getVisitingHero())
-				giveSpells(t,t->getVisitingHero());
-			if(t->getGarrisonHero())
-				giveSpells(t,t->getGarrisonHero());
-		}
-	};
-
 	//Checks if all requirements will be met with expected building list "buildingsThatWillBe"
 	auto areRequirementsFulfilled = [&buildingsThatWillBe](const BuildingID & buildID)
 	{
@@ -2192,8 +2176,20 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID,
 	sendAndApply(ns);
 
 	//Other post-built events. To some logic like giving spells to work gamestate changes for new building must be already in place!
-	for(auto builtID : ns.bid)
-		processAfterBuiltStructure(builtID);
+	for(auto buildingID : ns.bid)
+	{
+		bool isMageGuild = buildingID <= BuildingID::MAGES_GUILD_5 && buildingID >= BuildingID::MAGES_GUILD_1;
+		bool isLibrary = t->getTown()->buildings.at(buildingID)->subId == BuildingSubID::LIBRARY;
+		bool isAurora = t->getTown()->buildings.at(buildingID)->subId == BuildingSubID::AURORA_BOREALIS;
+
+		if(isMageGuild || isLibrary || isAurora)
+		{
+			if(t->getVisitingHero())
+				giveSpells(t,t->getVisitingHero());
+			if(t->getGarrisonHero())
+				giveSpells(t,t->getGarrisonHero());
+		}
+	};
 
 	// now when everything is built - reveal tiles for lookout tower
 	changeFogOfWar(t->getSightCenter(), t->getSightRadius(), t->getOwner(), ETileVisibility::REVEALED);

+ 1 - 1
server/battles/BattleActionProcessor.cpp

@@ -1448,7 +1448,7 @@ void BattleActionProcessor::applyBattleEffects(const CBattleInfoCallback & battl
 	}
 
 	//life drain handling
-	if(attackerState->hasBonusOfType(BonusType::LIFE_DRAIN) && def->isLiving())
+	if(attackerState->hasBonusOfType(BonusType::LIFE_DRAIN) && def->isLiving() && attackerState->getTotalHealth() != attackerState->getAvailableHealth())
 	{
 		int64_t toHeal = bsa.damageAmount * attackerState->valOfBonuses(BonusType::LIFE_DRAIN) / 100;
 		healInfo += attackerState->heal(toHeal, EHealLevel::RESURRECT, EHealPower::PERMANENT);

+ 1 - 1
server/processors/NewTurnProcessor.cpp

@@ -512,7 +512,7 @@ std::tuple<EWeekType, CreatureID> NewTurnProcessor::pickWeekType(bool newMonth)
 	for (const auto & townID : gameHandler->gameState().getMap().getAllTowns())
 	{
 		const auto * t = gameHandler->gameState().getTown(townID);
-		if (t->hasBuilt(BuildingID::GRAIL, ETownType::INFERNO))
+		if (t->hasBuilt(BuildingSubID::DEITY_OF_FIRE))
 			return { EWeekType::DEITYOFFIRE, CreatureID::IMP };
 	}