2022-07-26 15:07:42 +02:00
|
|
|
/*
|
2014-06-05 20:26:50 +03:00
|
|
|
* CGPandoraBox.cpp, part of VCMI engine
|
2014-06-05 19:52:14 +03:00
|
|
|
*
|
|
|
|
* 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 "CGPandoraBox.h"
|
|
|
|
|
Entities redesign and a few ERM features
* Made most Handlers derived from CHandlerBase and moved service API there.
* Declared existing Entity APIs.
* Added basic script context caching
* Started Lua script module
* Started Lua spell effect API
* Started script state persistence
* Started battle info callback binding
* CommitPackage removed
* Extracted spells::Caster to own header; Expanded Spell API.
* implemented !!MC:S, !!FU:E, !!FU:P, !!MA, !!VR:H, !!VR:C
* !!BU:C, !!BU:E, !!BU:G, !!BU:M implemented
* Allow use of "MC:S@varName@" to declare normal variable (technically v-variable with string key)
* Re-enabled VERM macros.
* !?GM0 added
* !?TM implemented
* Added !!MF:N
* Started !?OB, !!BM, !!HE, !!OW, !!UN
* Added basic support of w-variables
* Added support for ERM indirect variables
* Made !?FU regular trigger
* !!re (ERA loop receiver) implemented
* Fixed ERM receivers with zero args.
2018-03-17 16:58:30 +02:00
|
|
|
#include <vcmi/spells/Spell.h>
|
|
|
|
#include <vcmi/spells/Service.h>
|
|
|
|
|
2014-06-05 20:26:50 +03:00
|
|
|
#include "../NetPacks.h"
|
2014-06-05 23:51:24 +03:00
|
|
|
#include "../CSoundBase.h"
|
2014-06-05 19:52:14 +03:00
|
|
|
|
2018-03-31 07:56:40 +02:00
|
|
|
#include "../CSkillHandler.h"
|
2014-06-25 17:11:07 +03:00
|
|
|
#include "../StartInfo.h"
|
|
|
|
#include "../IGameCallback.h"
|
2023-08-19 23:22:31 +02:00
|
|
|
#include "../constants/StringConstants.h"
|
2016-11-13 12:38:42 +02:00
|
|
|
#include "../serializer/JsonSerializeFormat.h"
|
2014-06-05 19:52:14 +03:00
|
|
|
|
2022-07-26 15:07:42 +02:00
|
|
|
VCMI_LIB_NAMESPACE_BEGIN
|
|
|
|
|
2023-09-15 15:29:41 +02:00
|
|
|
void CGPandoraBox::init()
|
2014-06-05 19:52:14 +03:00
|
|
|
{
|
2023-09-15 15:29:41 +02:00
|
|
|
blockVisit = true;
|
2023-09-15 21:08:14 +02:00
|
|
|
configuration.selectMode = Rewardable::SELECT_ALL;
|
2023-09-15 15:29:41 +02:00
|
|
|
|
|
|
|
for(auto & i : configuration.info)
|
|
|
|
i.reward.removeObject = true;
|
2014-06-05 19:52:14 +03:00
|
|
|
}
|
|
|
|
|
2023-09-15 15:29:41 +02:00
|
|
|
void CGPandoraBox::initObj(CRandomGenerator & rand)
|
2014-06-05 19:52:14 +03:00
|
|
|
{
|
2023-09-15 15:29:41 +02:00
|
|
|
init();
|
|
|
|
|
|
|
|
CRewardableObject::initObj(rand);
|
2014-06-05 19:52:14 +03:00
|
|
|
}
|
|
|
|
|
2023-09-15 15:29:41 +02:00
|
|
|
void CGPandoraBox::onHeroVisit(const CGHeroInstance * h) const
|
2014-06-05 19:52:14 +03:00
|
|
|
{
|
2023-09-15 15:29:41 +02:00
|
|
|
auto setText = [](MetaString & text, int tId, const CGHeroInstance * h)
|
2014-06-05 19:52:14 +03:00
|
|
|
{
|
2023-09-15 21:13:07 +02:00
|
|
|
text.clear();
|
2023-09-15 15:29:41 +02:00
|
|
|
text.appendLocalString(EMetaText::ADVOB_TXT, tId);
|
|
|
|
text.replaceRawString(h->getNameTranslated());
|
|
|
|
};
|
|
|
|
|
|
|
|
for(auto i : getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT))
|
2014-06-05 19:52:14 +03:00
|
|
|
{
|
2023-09-15 15:29:41 +02:00
|
|
|
MetaString txt;
|
|
|
|
auto & r = configuration.info[i];
|
|
|
|
|
|
|
|
if(r.reward.spells.size() == 1)
|
|
|
|
setText(txt, 184, h);
|
|
|
|
else if(!r.reward.spells.empty())
|
|
|
|
setText(txt, 188, h);
|
|
|
|
|
|
|
|
if(r.reward.heroExperience || !r.reward.secondary.empty())
|
|
|
|
setText(txt, 175, h);
|
|
|
|
|
|
|
|
for(int ps : r.reward.primary)
|
2016-09-20 12:40:58 +02:00
|
|
|
{
|
2023-09-15 15:29:41 +02:00
|
|
|
if(ps)
|
|
|
|
{
|
|
|
|
setText(txt, 175, h);
|
|
|
|
break;
|
|
|
|
}
|
2016-09-19 20:05:57 +02:00
|
|
|
}
|
2023-09-15 15:29:41 +02:00
|
|
|
|
|
|
|
if(r.reward.manaDiff < 0)
|
|
|
|
setText(txt, 176, h);
|
|
|
|
if(r.reward.manaDiff > 0)
|
|
|
|
setText(txt, 177, h);
|
|
|
|
|
|
|
|
for(auto b : r.reward.bonuses)
|
2014-06-05 19:52:14 +03:00
|
|
|
{
|
2023-09-15 15:29:41 +02:00
|
|
|
if(b.type == BonusType::MORALE)
|
2014-06-05 19:52:14 +03:00
|
|
|
{
|
2023-09-15 15:29:41 +02:00
|
|
|
if(b.val < 0)
|
|
|
|
setText(txt, 178, h);
|
|
|
|
if(b.val > 0)
|
|
|
|
setText(txt, 179, h);
|
2016-06-06 09:30:45 +02:00
|
|
|
}
|
2023-09-15 15:29:41 +02:00
|
|
|
if(b.type == BonusType::LUCK)
|
2016-06-06 09:30:45 +02:00
|
|
|
{
|
2023-09-15 15:29:41 +02:00
|
|
|
if(b.val < 0)
|
|
|
|
setText(txt, 180, h);
|
|
|
|
if(b.val > 0)
|
|
|
|
setText(txt, 181, h);
|
2014-06-05 19:52:14 +03:00
|
|
|
}
|
|
|
|
}
|
2023-09-15 15:29:41 +02:00
|
|
|
|
|
|
|
for(auto res : r.reward.resources)
|
2014-06-05 19:52:14 +03:00
|
|
|
{
|
2023-09-15 15:29:41 +02:00
|
|
|
if(res < 0)
|
|
|
|
setText(txt, 182, h);
|
|
|
|
if(res > 0)
|
|
|
|
setText(txt, 183, h);
|
2014-06-05 19:52:14 +03:00
|
|
|
}
|
2023-09-15 15:29:41 +02:00
|
|
|
|
|
|
|
if(!r.reward.artifacts.empty())
|
|
|
|
setText(txt, 183, h);
|
|
|
|
|
|
|
|
if(!r.reward.creatures.empty())
|
|
|
|
{
|
|
|
|
MetaString loot;
|
|
|
|
for(auto c : r.reward.creatures)
|
|
|
|
{
|
|
|
|
loot.appendRawString("%s");
|
|
|
|
loot.replaceCreatureName(c);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(r.reward.creatures.size() == 1 && r.reward.creatures[0].count == 1)
|
|
|
|
txt.appendLocalString(EMetaText::ADVOB_TXT, 185);
|
|
|
|
else
|
|
|
|
txt.appendLocalString(EMetaText::ADVOB_TXT, 186);
|
|
|
|
|
|
|
|
txt.replaceRawString(loot.buildList());
|
|
|
|
txt.replaceRawString(h->getNameTranslated());
|
2014-06-05 19:52:14 +03:00
|
|
|
}
|
2023-09-15 15:29:41 +02:00
|
|
|
|
|
|
|
const_cast<MetaString&>(r.message) = txt;
|
2014-06-05 19:52:14 +03:00
|
|
|
}
|
2023-09-15 15:29:41 +02:00
|
|
|
|
|
|
|
BlockingDialog bd (true, false);
|
|
|
|
bd.player = h->getOwner();
|
|
|
|
bd.text.appendLocalString(EMetaText::ADVOB_TXT, 14);
|
|
|
|
cb->showBlockingDialog(&bd);
|
2014-06-05 19:52:14 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void CGPandoraBox::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
|
|
|
|
{
|
2016-01-26 07:41:09 +02:00
|
|
|
if(result.winner == 0)
|
|
|
|
{
|
2023-09-15 15:29:41 +02:00
|
|
|
CRewardableObject::onHeroVisit(hero);
|
2016-01-26 07:41:09 +02:00
|
|
|
}
|
2014-06-05 19:52:14 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void CGPandoraBox::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
|
|
|
|
{
|
2015-12-24 20:30:57 +02:00
|
|
|
if(answer)
|
2014-06-05 19:52:14 +03:00
|
|
|
{
|
2015-12-24 20:30:57 +02:00
|
|
|
if(stacksCount() > 0) //if pandora's box is protected by army
|
2014-06-05 19:52:14 +03:00
|
|
|
{
|
2023-03-08 00:32:21 +02:00
|
|
|
hero->showInfoDialog(16, 0, EInfoWindowMode::MODAL);
|
2014-06-05 19:52:14 +03:00
|
|
|
cb->startBattleI(hero, this); //grants things after battle
|
|
|
|
}
|
2023-09-15 15:29:41 +02:00
|
|
|
else if(getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT).empty())
|
2014-06-05 19:52:14 +03:00
|
|
|
{
|
2023-03-08 00:32:21 +02:00
|
|
|
hero->showInfoDialog(15);
|
2014-06-05 19:52:14 +03:00
|
|
|
cb->removeObject(this);
|
|
|
|
}
|
|
|
|
else //if it gives something without battle
|
|
|
|
{
|
2023-09-15 15:29:41 +02:00
|
|
|
CRewardableObject::onHeroVisit(hero);
|
2014-06-05 19:52:14 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-13 12:38:42 +02:00
|
|
|
void CGPandoraBox::serializeJsonOptions(JsonSerializeFormat & handler)
|
|
|
|
{
|
2023-09-15 15:29:41 +02:00
|
|
|
CRewardableObject::serializeJsonOptions(handler);
|
2016-11-13 12:38:42 +02:00
|
|
|
handler.serializeString("guardMessage", message);
|
2023-09-15 15:29:41 +02:00
|
|
|
|
|
|
|
if(!handler.saving)
|
2016-11-13 12:38:42 +02:00
|
|
|
{
|
2023-09-15 15:29:41 +02:00
|
|
|
//backward compatibility
|
|
|
|
CCreatureSet::serializeJson(handler, "guards", 7);
|
2023-09-15 21:08:14 +02:00
|
|
|
Rewardable::Reward reward;
|
|
|
|
|
|
|
|
auto addReward = [this, &reward](bool condition)
|
|
|
|
{
|
|
|
|
if(condition)
|
|
|
|
{
|
|
|
|
configuration.info.emplace_back();
|
|
|
|
configuration.info.back().visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
|
|
|
|
configuration.info.back().reward = reward;
|
2023-09-15 21:13:07 +02:00
|
|
|
reward = Rewardable::Reward{};
|
2023-09-15 21:08:14 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
addReward(true);
|
2023-09-15 15:29:41 +02:00
|
|
|
|
|
|
|
int val;
|
|
|
|
handler.serializeInt("experience", reward.heroExperience, 0);
|
2023-09-15 21:08:14 +02:00
|
|
|
addReward(reward.heroExperience);
|
|
|
|
|
2023-09-15 15:29:41 +02:00
|
|
|
handler.serializeInt("mana", reward.manaDiff, 0);
|
2023-09-15 21:08:14 +02:00
|
|
|
addReward(reward.manaDiff);
|
|
|
|
|
2023-09-15 15:29:41 +02:00
|
|
|
handler.serializeInt("morale", val, 0);
|
|
|
|
if(val)
|
|
|
|
reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::MORALE, BonusSource::OBJECT, val, id);
|
2023-09-15 21:08:14 +02:00
|
|
|
addReward(val);
|
|
|
|
|
2023-09-15 15:29:41 +02:00
|
|
|
handler.serializeInt("luck", val, 0);
|
|
|
|
if(val)
|
|
|
|
reward.bonuses.emplace_back(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT, val, id);
|
2023-09-15 21:08:14 +02:00
|
|
|
addReward(val);
|
2023-09-15 15:29:41 +02:00
|
|
|
|
|
|
|
reward.resources.serializeJson(handler, "resources");
|
2023-09-15 21:08:14 +02:00
|
|
|
addReward(reward.resources.nonZero());
|
|
|
|
|
2017-07-20 06:08:49 +02:00
|
|
|
{
|
2023-09-15 21:08:14 +02:00
|
|
|
bool updateReward = false;
|
2017-07-20 06:08:49 +02:00
|
|
|
auto s = handler.enterStruct("primarySkills");
|
2023-09-15 15:29:41 +02:00
|
|
|
for(int idx = 0; idx < reward.primary.size(); idx ++)
|
2023-09-15 21:08:14 +02:00
|
|
|
{
|
2023-09-15 15:29:41 +02:00
|
|
|
handler.serializeInt(NPrimarySkill::names[idx], reward.primary[idx], 0);
|
2023-09-15 21:08:14 +02:00
|
|
|
updateReward |= reward.primary[idx];
|
|
|
|
}
|
|
|
|
addReward(updateReward);
|
2017-07-20 06:08:49 +02:00
|
|
|
}
|
2023-09-15 15:29:41 +02:00
|
|
|
|
|
|
|
handler.serializeIdArray("artifacts", reward.artifacts);
|
2023-09-15 21:08:14 +02:00
|
|
|
addReward(!reward.artifacts.empty());
|
|
|
|
|
2023-09-15 15:29:41 +02:00
|
|
|
handler.serializeIdArray("spells", reward.spells);
|
2023-09-15 21:08:14 +02:00
|
|
|
addReward(!reward.spells.empty());
|
2016-11-13 12:38:42 +02:00
|
|
|
|
2023-09-15 15:29:41 +02:00
|
|
|
handler.enterArray("creatures").serializeStruct(reward.creatures);
|
2023-09-15 21:08:14 +02:00
|
|
|
addReward(!reward.creatures.empty());
|
2023-09-15 15:29:41 +02:00
|
|
|
|
2016-11-13 12:38:42 +02:00
|
|
|
{
|
2023-09-15 21:08:14 +02:00
|
|
|
bool updateReward = false;
|
2016-11-13 12:38:42 +02:00
|
|
|
auto s = handler.enterStruct("secondarySkills");
|
2023-09-15 15:29:41 +02:00
|
|
|
for(const auto & p : handler.getCurrent().Struct())
|
2016-11-13 12:38:42 +02:00
|
|
|
{
|
2023-09-15 15:29:41 +02:00
|
|
|
const std::string skillName = p.first;
|
|
|
|
const std::string levelId = p.second.String();
|
|
|
|
|
|
|
|
const int rawId = CSkillHandler::decodeSkill(skillName);
|
|
|
|
if(rawId < 0)
|
|
|
|
{
|
|
|
|
logGlobal->error("Invalid secondary skill %s", skillName);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const int level = vstd::find_pos(NSecondarySkill::levels, levelId);
|
|
|
|
if(level < 0)
|
|
|
|
{
|
|
|
|
logGlobal->error("Invalid secondary skill level %s", levelId);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
reward.secondary[rawId] = level;
|
2023-09-15 21:08:14 +02:00
|
|
|
updateReward = true;
|
2016-11-13 12:38:42 +02:00
|
|
|
}
|
2023-09-15 21:08:14 +02:00
|
|
|
addReward(updateReward);
|
2016-11-13 12:38:42 +02:00
|
|
|
}
|
|
|
|
}
|
2023-09-15 15:29:41 +02:00
|
|
|
}
|
2016-11-13 12:38:42 +02:00
|
|
|
|
2023-09-15 15:29:41 +02:00
|
|
|
void CGEvent::init()
|
|
|
|
{
|
|
|
|
blockVisit = false;
|
2023-09-15 21:08:14 +02:00
|
|
|
configuration.selectMode = Rewardable::SELECT_ALL;
|
2023-09-15 15:29:41 +02:00
|
|
|
|
|
|
|
for(auto & i : configuration.info)
|
|
|
|
i.reward.removeObject = removeAfterVisit;
|
2016-11-13 12:38:42 +02:00
|
|
|
}
|
|
|
|
|
2014-06-05 19:52:14 +03:00
|
|
|
void CGEvent::onHeroVisit( const CGHeroInstance * h ) const
|
|
|
|
{
|
2023-08-25 20:40:19 +02:00
|
|
|
if(availableFor.count(h->tempOwner) == 0)
|
2014-06-05 19:52:14 +03:00
|
|
|
return;
|
2023-08-25 20:40:19 +02:00
|
|
|
|
2018-01-05 19:21:07 +02:00
|
|
|
if(cb->getPlayerSettings(h->tempOwner)->isControlledByHuman())
|
2014-06-05 19:52:14 +03:00
|
|
|
{
|
|
|
|
if(humanActivate)
|
|
|
|
activated(h);
|
|
|
|
}
|
|
|
|
else if(computerActivate)
|
|
|
|
activated(h);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CGEvent::activated( const CGHeroInstance * h ) const
|
|
|
|
{
|
|
|
|
if(stacksCount() > 0)
|
|
|
|
{
|
|
|
|
InfoWindow iw;
|
|
|
|
iw.player = h->tempOwner;
|
2023-02-12 22:39:17 +02:00
|
|
|
if(!message.empty())
|
2023-06-18 11:18:25 +02:00
|
|
|
iw.text.appendRawString(message);
|
2014-06-05 19:52:14 +03:00
|
|
|
else
|
2023-06-18 11:18:25 +02:00
|
|
|
iw.text.appendLocalString(EMetaText::ADVOB_TXT, 16);
|
2014-06-05 19:52:14 +03:00
|
|
|
cb->showInfoDialog(&iw);
|
|
|
|
cb->startBattleI(h, this);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-09-15 15:29:41 +02:00
|
|
|
CRewardableObject::onHeroVisit(h);
|
2015-11-28 22:03:26 +02:00
|
|
|
}
|
|
|
|
}
|
2016-11-13 12:38:42 +02:00
|
|
|
|
|
|
|
void CGEvent::serializeJsonOptions(JsonSerializeFormat & handler)
|
|
|
|
{
|
|
|
|
CGPandoraBox::serializeJsonOptions(handler);
|
|
|
|
|
2023-09-15 15:29:41 +02:00
|
|
|
handler.serializeBool("aIActivable", computerActivate, false);
|
|
|
|
handler.serializeBool("humanActivable", humanActivate, true);
|
|
|
|
handler.serializeBool("removeAfterVisit", removeAfterVisit, false);
|
2023-08-25 20:40:19 +02:00
|
|
|
handler.serializeIdArray("availableFor", availableFor);
|
2016-11-13 12:38:42 +02:00
|
|
|
}
|
2022-07-26 15:07:42 +02:00
|
|
|
|
|
|
|
VCMI_LIB_NAMESPACE_END
|