1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-23 22:37:55 +02:00

Necromancy & Eagle eye infowindows

This commit is contained in:
SoundSSGood
2025-04-07 20:10:14 +02:00
parent 603672ff51
commit 60afbbe20f
13 changed files with 175 additions and 118 deletions

View File

@@ -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;
}

View File

@@ -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;
};

View File

@@ -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)

View File

@@ -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
client/UIHelper.cpp Normal file
View File

@@ -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
client/UIHelper.h Normal file
View File

@@ -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);
}

View File

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

View File

@@ -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
{

View File

@@ -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;
//////////////////////////////////////////////////////////////////////////

View File

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

View File

@@ -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);
}
};

View File

@@ -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)
{

View File

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