Browse Source

Necromancy & Eagle eye infowindows

SoundSSGood 8 months ago
parent
commit
60afbbe20f

+ 0 - 16
client/ArtifactsUIController.cpp

@@ -167,19 +167,3 @@ void ArtifactsUIController::artifactDisassembled()
 	for(const auto & artWin : ENGINE->windows().findWindows<CWindowWithArtifacts>())
 		artWin->update();
 }
-
-std::vector<Component> ArtifactsUIController::getMovedComponents(const CArtifactSet & artSet, const std::vector<MoveArtifactInfo> & movedPack) const
-{
-	std::vector<Component> components;
-	for(const auto & artMoveInfo : movedPack)
-	{
-		const auto art = artSet.getArt(artMoveInfo.dstPos);
-		assert(art);
-
-		if(art->isScroll())
-			components.emplace_back(ComponentType::SPELL_SCROLL, art->getScrollSpellID());
-		else
-			components.emplace_back(ComponentType::ARTIFACT, art->getTypeId());
-	}
-	return components;
-}

+ 0 - 3
client/ArtifactsUIController.h

@@ -14,9 +14,7 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-class CArtifactSet;
 class CGHeroInstance;
-struct Component;
 
 VCMI_LIB_NAMESPACE_END
 
@@ -38,5 +36,4 @@ public:
 	void bulkArtMovementStart(size_t totalNumOfArts, size_t possibleAssemblyNumOfArts);
 	void artifactAssembled();
 	void artifactDisassembled();
-	std::vector<Component> getMovedComponents(const CArtifactSet & artSet, const std::vector<MoveArtifactInfo> & movedPack) const;
 };

+ 2 - 0
client/CMakeLists.txt

@@ -191,6 +191,7 @@ set(vcmiclientcommon_SRCS
 	NetPacksClient.cpp
 	NetPacksLobbyClient.cpp
 	ServerRunner.cpp
+	UIHelper.cpp
 )
 
 set(vcmiclientcommon_HEADERS
@@ -410,6 +411,7 @@ set(vcmiclientcommon_HEADERS
 	LobbyClientNetPackVisitors.h
 	ServerRunner.h
 	resource.h
+	UIHelper.h
 )
 
 if(APPLE_IOS)

+ 20 - 7
client/NetPacksClient.cpp

@@ -26,6 +26,7 @@
 #include "CMT.h"
 #include "GameChatHandler.h"
 #include "CServerHandler.h"
+#include "UIHelper.h"
 
 #include "../CCallback.h"
 #include "../lib/filesystem/Filesystem.h"
@@ -851,22 +852,34 @@ void ApplyClientNetPackVisitor::visitStacksInjured(StacksInjured & pack)
 
 void ApplyClientNetPackVisitor::visitBattleResultsApplied(BattleResultsApplied & pack)
 {
-	InfoWindow win;
-	win.player = pack.victor;
-	win.text.appendLocalString(EMetaText::GENERAL_TXT, 30);
+	if(!pack.learnedSpells.spells.empty())
+	{
+		const auto hero = GAME->interface()->cb->getHero(pack.learnedSpells.hid);
+		assert(hero);
+		callInterfaceIfPresent(cl, pack.victor, &CGameInterface::showInfoDialog, EInfoWindowMode::MODAL,
+			UIHelper::getEagleEyeInfoWindowText(*hero, pack.learnedSpells.spells), UIHelper::getSpellsComponents(pack.learnedSpells.spells), soundBase::soundID(0));
+	}
+
 	const auto artSet = GAME->interface()->cb->getArtSet(ArtifactLocation(pack.artifacts.front().dstArtHolder));
 	assert(artSet);
+	std::vector<Component> artComponents;
 	for(const auto & artPack : pack.artifacts)
 	{
-		auto packComponents = GAME->interface()->artifactController->getMovedComponents(*artSet, artPack.artsPack0);
-		win.components.insert(win.components.end(), std::make_move_iterator(packComponents.begin()), std::make_move_iterator(packComponents.end()));
+		auto packComponents = UIHelper::getArtifactsComponents(*artSet, artPack.artsPack0);
+		artComponents.insert(artComponents.end(), std::make_move_iterator(packComponents.begin()), std::make_move_iterator(packComponents.end()));
 	}
-	if(!win.components.empty())
-		visitInfoWindow(win);
+	if(!artComponents.empty())
+		callInterfaceIfPresent(cl, pack.victor, &CGameInterface::showInfoDialog, EInfoWindowMode::MODAL, UIHelper::getArtifactsInfoWindowText(),
+			artComponents, soundBase::soundID(0));
 
 	for(auto & artPack : pack.artifacts)
 		visitBulkMoveArtifacts(artPack);
 
+	if(pack.raisedStack.getCreature())
+		callInterfaceIfPresent(cl, pack.victor, &CGameInterface::showInfoDialog, EInfoWindowMode::AUTO,
+			UIHelper::getNecromancyInfoWindowText(pack.raisedStack), std::vector<Component>{Component(ComponentType::CREATURE, pack.raisedStack.getId(),
+			pack.raisedStack.count)}, UIHelper::getNecromancyInfoWindowSound());
+
 	callInterfaceIfPresent(cl, pack.victor, &IGameEventsReceiver::battleResultsApplied);
 	callInterfaceIfPresent(cl, pack.loser, &IGameEventsReceiver::battleResultsApplied);
 	callInterfaceIfPresent(cl, PlayerColor::SPECTATOR, &IGameEventsReceiver::battleResultsApplied);

+ 93 - 0
client/UIHelper.cpp

@@ -0,0 +1,93 @@
+/*
+ * UIHelper.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
+ *
+ */
+
+#include "StdInc.h"
+#include "UIHelper.h"
+
+#include "widgets/CComponent.h"
+
+#include "../lib/CArtHandler.h"
+#include "../lib/CArtifactInstance.h"
+#include "../lib/mapObjects/CGHeroInstance.h"
+#include "../lib/networkPacks/ArtifactLocation.h"
+#include "../lib/CRandomGenerator.h"
+#include "../lib/CCreatureSet.h"
+
+std::vector<Component> UIHelper::getArtifactsComponents(const CArtifactSet & artSet, const std::vector<MoveArtifactInfo> & movedPack)
+{
+    std::vector<Component> components;
+    for(const auto & artMoveInfo : movedPack)
+    {
+        const auto art = artSet.getArt(artMoveInfo.dstPos);
+        assert(art);
+
+        if(art->isScroll())
+            components.emplace_back(ComponentType::SPELL_SCROLL, art->getScrollSpellID());
+        else
+            components.emplace_back(ComponentType::ARTIFACT, art->getTypeId());
+    }
+    return components;
+}
+
+std::vector<Component> UIHelper::getSpellsComponents(const std::set<SpellID> & spells)
+{
+    std::vector<Component> components;
+    for(const auto & spell : spells)
+        components.emplace_back(ComponentType::SPELL, spell);
+    return components;
+}
+
+soundBase::soundID UIHelper::getNecromancyInfoWindowSound()
+{
+    return soundBase::soundID(soundBase::pickup01 + CRandomGenerator::getDefault().nextInt(6));
+}
+
+std::string UIHelper::getNecromancyInfoWindowText(const CStackBasicDescriptor & stack)
+{
+    MetaString text;
+    if(stack.count > 1) // Practicing the dark arts of necromancy, ... (plural)
+    {
+        text.appendLocalString(EMetaText::GENERAL_TXT, 145);
+        text.replaceNumber(stack.count);
+    }
+    else // Practicing the dark arts of necromancy, ... (singular)
+    {
+        text.appendLocalString(EMetaText::GENERAL_TXT, 146);
+    }
+    text.replaceName(stack);
+    return text.toString();
+}
+
+std::string UIHelper::getArtifactsInfoWindowText()
+{
+    MetaString text;
+    text.appendLocalString(EMetaText::GENERAL_TXT, 30);
+    return text.toString();
+}
+
+std::string UIHelper::getEagleEyeInfoWindowText(const CGHeroInstance & hero, const std::set<SpellID> & spells)
+{
+    MetaString text;
+    text.appendLocalString(EMetaText::GENERAL_TXT, 221); // Through eagle-eyed observation, %s is able to learn %s
+    text.replaceRawString(hero.getNameTranslated());
+
+    auto curSpell = spells.begin();
+    text.replaceName(*curSpell++);
+    for(int i = 1; i < spells.size(); i++, curSpell++)
+    {
+        if(i + 1 == spells.size())
+            text.appendLocalString(EMetaText::GENERAL_TXT, 141); // " and "
+        else
+            text.appendRawString(", ");
+        text.appendName(*curSpell);
+    }
+    text.appendRawString(".");
+    return text.toString();
+}

+ 35 - 0
client/UIHelper.h

@@ -0,0 +1,35 @@
+/*
+ * UIHelper.h, 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
+ *
+ */
+#pragma once
+
+#include "StdInc.h"
+
+#include "../lib/CSoundBase.h"
+#include "../lib/texts/MetaString.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+struct MoveArtifactInfo;
+struct Component;
+class CArtifactSet;
+class CGHeroInstance;
+class CStackBasicDescriptor;
+
+VCMI_LIB_NAMESPACE_END
+
+namespace UIHelper
+{
+    std::vector<Component> getArtifactsComponents(const CArtifactSet & artSet, const std::vector<MoveArtifactInfo> & movedPack);
+    std::vector<Component> getSpellsComponents(const std::set<SpellID> & spells);
+    soundBase::soundID getNecromancyInfoWindowSound();
+    std::string getNecromancyInfoWindowText(const CStackBasicDescriptor & stack);
+    std::string getArtifactsInfoWindowText();
+    std::string getEagleEyeInfoWindowText(const CGHeroInstance & hero, const std::set<SpellID> & spells);
+}

+ 0 - 1
client/windows/CWindowWithArtifacts.cpp

@@ -12,7 +12,6 @@
 
 #include "CHeroWindow.h"
 #include "CSpellWindow.h"
-#include "CExchangeWindow.h"
 #include "CHeroBackpackWindow.h"
 
 #include "../GameEngine.h"

+ 1 - 27
lib/mapObjects/CGHeroInstance.cpp

@@ -968,7 +968,7 @@ CStackBasicDescriptor CGHeroInstance::calculateNecromancy (const BattleResult &b
 		double necromancySkill = valOfBonuses(BonusType::UNDEAD_RAISE_PERCENTAGE) / 100.0;
 		const ui8 necromancyLevel = valOfBonuses(BonusType::IMPROVED_NECROMANCY);
 		vstd::amin(necromancySkill, 1.0); //it's impossible to raise more creatures than all...
-		const std::map<CreatureID,si32> casualties {}; //= battleResult.casualties[CBattleInfoEssentials::otherSide(battleResult.winner)];
+		const std::map<CreatureID,si32> &casualties = battleResult.casualties[CBattleInfoEssentials::otherSide(battleResult.winner)];
 		// figure out what to raise - pick strongest creature meeting requirements
 		CreatureID creatureTypeRaised = CreatureID::NONE; //now we always have IMPROVED_NECROMANCY, no need for hardcode
 		int requiredCasualtyLevel = 1;
@@ -1037,32 +1037,6 @@ CStackBasicDescriptor CGHeroInstance::calculateNecromancy (const BattleResult &b
 	return CStackBasicDescriptor();
 }
 
-/**
- * Show the necromancy dialog with information about units raised.
- * @param raisedStack Pair where the first element represents ID of the raised creature
- * and the second element the amount.
- */
-void CGHeroInstance::showNecromancyDialog(const CStackBasicDescriptor &raisedStack, vstd::RNG & rand) const
-{
-	InfoWindow iw;
-	iw.type = EInfoWindowMode::AUTO;
-	iw.soundID = soundBase::pickup01 + rand.nextInt(6);
-	iw.player = tempOwner;
-	iw.components.emplace_back(ComponentType::CREATURE, raisedStack.getId(), raisedStack.count);
-
-	if (raisedStack.count > 1) // Practicing the dark arts of necromancy, ... (plural)
-	{
-		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 145);
-		iw.text.replaceNumber(raisedStack.count);
-	}
-	else // Practicing the dark arts of necromancy, ... (singular)
-	{
-		iw.text.appendLocalString(EMetaText::GENERAL_TXT, 146);
-	}
-	iw.text.replaceName(raisedStack);
-
-	cb->showInfoDialog(&iw);
-}
 /*
 int3 CGHeroInstance::getSightCenter() const
 {

+ 0 - 1
lib/mapObjects/CGHeroInstance.h

@@ -241,7 +241,6 @@ public:
 	int getBasePrimarySkillValue(PrimarySkill which) const; //the value of a base-skill without items or temporary bonuses
 
 	CStackBasicDescriptor calculateNecromancy (const BattleResult &battleResult) const;
-	void showNecromancyDialog(const CStackBasicDescriptor &raisedStack, vstd::RNG & rand) const;
 	EDiggingStatus diggingStatus() const;
 
 	//////////////////////////////////////////////////////////////////////////

+ 2 - 0
lib/networkPacks/NetPacksLib.cpp

@@ -2306,6 +2306,8 @@ void BattleUnitsChanged::applyBattle(IBattleState * battleState)
 
 void BattleResultsApplied::applyGs(CGameState * gs)
 {
+	learnedSpells.applyGs(gs);
+
 	for(auto & artPack : artifacts)
 		artPack.applyGs(gs);
 

+ 6 - 2
lib/networkPacks/PacksForClientBattle.h

@@ -96,8 +96,8 @@ struct DLL_LINKAGE BattleResultAccepted : public CPackForClient
 	struct HeroBattleResults
 	{
 		HeroBattleResults()
-			: armyId(ObjectInstanceID::NONE)
-			, heroId(ObjectInstanceID::NONE)
+			: heroId(ObjectInstanceID::NONE)
+			, armyId(ObjectInstanceID::NONE)
 			, exp(0) {}
 
 		ObjectInstanceID heroId;
@@ -426,7 +426,9 @@ struct DLL_LINKAGE BattleResultsApplied : public CPackForClient
 	BattleID battleID = BattleID::NONE;
 	PlayerColor victor;
 	PlayerColor loser;
+	ChangeSpells learnedSpells;
 	std::vector<BulkMoveArtifacts> artifacts;
+	CStackBasicDescriptor raisedStack;
 	void visitTyped(ICPackVisitor & visitor) override;
 	void applyGs(CGameState *gs) override;
 
@@ -435,7 +437,9 @@ struct DLL_LINKAGE BattleResultsApplied : public CPackForClient
 		h & battleID;
 		h & victor;
 		h & loser;
+		h & learnedSpells;
 		h & artifacts;
+		h & raisedStack;
 		assert(battleID != BattleID::NONE);
 	}
 };

+ 16 - 60
server/battles/BattleResultProcessor.cpp

@@ -21,17 +21,12 @@
 #include "../../lib/CStack.h"
 #include "../../lib/CPlayerState.h"
 #include "../../lib/IGameSettings.h"
-#include "../../lib/battle/CBattleInfoCallback.h"
-#include "../../lib/battle/IBattleState.h"
 #include "../../lib/battle/SideInBattle.h"
 #include "../../lib/gameState/CGameState.h"
 #include "../../lib/mapObjects/CGTownInstance.h"
 #include "../../lib/networkPacks/PacksForClientBattle.h"
-#include "../../lib/networkPacks/PacksForClient.h"
 #include "../../lib/spells/CSpellHandler.h"
 
-#include <vstd/RNG.h>
-
 #include <boost/lexical_cast.hpp>
 
 BattleResultProcessor::BattleResultProcessor(BattleProcessor * owner, CGameHandler * newGameHandler)
@@ -374,7 +369,7 @@ void BattleResultProcessor::endBattleConfirm(const CBattleInfoCallback & battle)
 	raccepted.winnerSide = finishingBattle->winnerSide;
 	gameHandler->sendAndApply(raccepted);
 
-	//--> continuation (battleAfterLevelUp) occurs after level-up gameHandler->queries are handled or on removing query
+	//--> continuation (battleFinalize) occurs after level-up gameHandler->queries are handled or on removing query
 }
 
 void BattleResultProcessor::battleFinalize(const BattleID & battleID, const BattleResult & result)
@@ -399,9 +394,9 @@ void BattleResultProcessor::battleFinalize(const BattleID & battleID, const Batt
 	// Still, it looks like a hole.
 
 	const auto battle = std::find_if(gameHandler->gameState()->currentBattles.begin(), gameHandler->gameState()->currentBattles.end(),
-		[battleID](const auto & battle)
+		[battleID](const auto & desiredBattle)
 		{
-			return battle->battleID == battleID;
+			return desiredBattle->battleID == battleID;
 		});
 	assert(battle != gameHandler->gameState()->currentBattles.end());
 
@@ -412,58 +407,22 @@ void BattleResultProcessor::battleFinalize(const BattleID & battleID, const Batt
 	// Eagle Eye handling
 	if(!finishingBattle->isDraw() && winnerHero)
 	{
-		ChangeSpells spells;
-
 		if(auto eagleEyeLevel = winnerHero->valOfBonuses(BonusType::LEARN_BATTLE_SPELL_LEVEL_LIMIT))
 		{
-			auto eagleEyeChance = winnerHero->valOfBonuses(BonusType::LEARN_BATTLE_SPELL_CHANCE);
+			resultsApplied.learnedSpells.learn = 1;
+			resultsApplied.learnedSpells.hid = finishingBattle->winnerId;
 			for(const auto & spellId : (*battle)->getUsedSpells(CBattleInfoEssentials::otherSide(result.winner)))
 			{
-				auto spell = spellId.toEntity(LIBRARY->spells());
+				const auto spell = spellId.toEntity(LIBRARY->spells());
 				if(spell
 					&& spell->getLevel() <= eagleEyeLevel
 					&& !winnerHero->spellbookContainsSpell(spell->getId())
-					&& gameHandler->getRandomGenerator().nextInt(99) < eagleEyeChance)
+					&& gameHandler->getRandomGenerator().nextInt(99) < winnerHero->valOfBonuses(BonusType::LEARN_BATTLE_SPELL_CHANCE))
 				{
-					spells.spells.insert(spell->getId());
+					resultsApplied.learnedSpells.spells.insert(spell->getId());
 				}
 			}
 		}
-
-		if(!spells.spells.empty())
-		{
-			spells.learn = 1;
-			spells.hid = finishingBattle->winnerId;
-
-			InfoWindow iw;
-			iw.player = winnerHero->tempOwner;
-			iw.text.appendLocalString(EMetaText::GENERAL_TXT, 221); //Through eagle-eyed observation, %s is able to learn %s
-			iw.text.replaceRawString(winnerHero->getNameTranslated());
-
-			std::ostringstream names;
-			for(int i = 0; i < spells.spells.size(); i++)
-			{
-				names << "%s";
-				if(i < spells.spells.size() - 2)
-					names << ", ";
-				else if(i < spells.spells.size() - 1)
-					names << "%s";
-			}
-			names << ".";
-
-			iw.text.replaceRawString(names.str());
-
-			auto it = spells.spells.begin();
-			for(int i = 0; i < spells.spells.size(); i++, it++)
-			{
-				iw.text.replaceName(*it);
-				if(i == spells.spells.size() - 2) //we just added pre-last name
-					iw.text.replaceLocalString(EMetaText::GENERAL_TXT, 141); // " and "
-				iw.components.emplace_back(ComponentType::SPELL, *it);
-			}
-			gameHandler->sendAndApply(iw);
-			gameHandler->sendAndApply(spells);
-		}
 	}
 
 	// Artifacts handling
@@ -519,16 +478,14 @@ void BattleResultProcessor::battleFinalize(const BattleID & battleID, const Batt
 		}
 	}
 
-	// Necromancy if applicable.
-	const CStackBasicDescriptor raisedStack = winnerHero ? winnerHero->calculateNecromancy(result) : CStackBasicDescriptor();
-	// Give raised units to winner and show dialog, if any were raised,
-	// units will be given after casualties are taken
-	const SlotID necroSlot = raisedStack.getCreature() ? winnerHero->getSlotFor(raisedStack.getCreature()) : SlotID();
-
-	if (necroSlot != SlotID() && !finishingBattle->isDraw())
+	// Necromancy handling
+	// Give raised units to winner, if any were raised, units will be given after casualties are taken
+	if(winnerHero)
 	{
-		winnerHero->showNecromancyDialog(raisedStack, gameHandler->getRandomGenerator());
-		gameHandler->addToSlot(StackLocation(finishingBattle->winnerId, necroSlot), raisedStack.getCreature(), raisedStack.count);
+		resultsApplied.raisedStack = winnerHero->calculateNecromancy(result);
+		const SlotID necroSlot = resultsApplied.raisedStack.getCreature() ? winnerHero->getSlotFor(resultsApplied.raisedStack.getCreature()) : SlotID();
+		if(necroSlot != SlotID() && !finishingBattle->isDraw())
+			gameHandler->addToSlot(StackLocation(finishingBattle->winnerId, necroSlot), resultsApplied.raisedStack.getCreature(), resultsApplied.raisedStack.count);
 	}
 
 	resultsApplied.battleID = battleID;
@@ -537,8 +494,7 @@ void BattleResultProcessor::battleFinalize(const BattleID & battleID, const Batt
 	gameHandler->sendAndApply(resultsApplied);
 
 	//handle victory/loss of engaged players
-	std::set<PlayerColor> playerColors = {finishingBattle->loser, finishingBattle->victor};
-	gameHandler->checkVictoryLossConditions(playerColors);
+	gameHandler->checkVictoryLossConditions({finishingBattle->loser, finishingBattle->victor});
 
 	if (result.result == EBattleResult::SURRENDER)
 	{

+ 0 - 1
server/battles/BattleResultProcessor.h

@@ -17,7 +17,6 @@
 VCMI_LIB_NAMESPACE_BEGIN
 struct SideInBattle;
 struct BattleResult;
-struct BulkMoveArtifacts;
 class CBattleInfoCallback;
 class CGHeroInstance;
 class CArmedInstance;