mirror of
https://github.com/vcmi/vcmi.git
synced 2025-03-17 20:58:07 +02:00
Merge remote-tracking branch 'upstream/develop' into develop
This commit is contained in:
commit
a1a03d4b74
@ -296,7 +296,7 @@ ReachabilityInfo getReachabilityWithEnemyBypass(
|
||||
continue;
|
||||
|
||||
auto dmg = damageCache.getOriginalDamage(activeStack, unit, state);
|
||||
auto turnsToKill = unit->getAvailableHealth() / dmg;
|
||||
auto turnsToKill = unit->getAvailableHealth() / std::max(dmg, (int64_t)1);
|
||||
|
||||
vstd::amin(turnsToKill, 100);
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
#include <vcmi/events/EventBus.h>
|
||||
|
||||
#include "../../lib/battle/BattleLayout.h"
|
||||
#include "../../lib/CStack.h"
|
||||
#include "../../lib/ScriptHandler.h"
|
||||
#include "../../lib/networkPacks/PacksForClientBattle.h"
|
||||
@ -479,10 +480,9 @@ int3 HypotheticBattle::getLocation() const
|
||||
return int3(-1, -1, -1);
|
||||
}
|
||||
|
||||
bool HypotheticBattle::isCreatureBank() const
|
||||
BattleLayout HypotheticBattle::getLayout() const
|
||||
{
|
||||
// TODO
|
||||
return false;
|
||||
return subject->getBattle()->getLayout();
|
||||
}
|
||||
|
||||
int64_t HypotheticBattle::getTreeVersion() const
|
||||
|
@ -160,7 +160,7 @@ public:
|
||||
int64_t getActualDamage(const DamageRange & damage, int32_t attackerCount, vstd::RNG & rng) const override;
|
||||
std::vector<SpellID> getUsedSpells(BattleSide side) const override;
|
||||
int3 getLocation() const override;
|
||||
bool isCreatureBank() const override;
|
||||
BattleLayout getLayout() const override;
|
||||
|
||||
int64_t getTreeVersion() const;
|
||||
|
||||
|
@ -208,12 +208,6 @@ float TacticalAdvantageEngine::getTacticalAdvantage(const CArmedInstance * we, c
|
||||
enemyFlyers->setValue(enemyStructure.flyers);
|
||||
enemySpeed->setValue(enemyStructure.maxSpeed);
|
||||
|
||||
bool bank = dynamic_cast<const CBank *>(enemy);
|
||||
if(bank)
|
||||
bankPresent->setValue(1);
|
||||
else
|
||||
bankPresent->setValue(0);
|
||||
|
||||
const CGTownInstance * fort = dynamic_cast<const CGTownInstance *>(enemy);
|
||||
if(fort)
|
||||
castleWalls->setValue(fort->fortLevel());
|
||||
|
@ -20,25 +20,6 @@
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
ui64 FuzzyHelper::estimateBankDanger(const CBank * bank)
|
||||
{
|
||||
//this one is not fuzzy anymore, just calculate weighted average
|
||||
|
||||
auto objectInfo = bank->getObjectHandler()->getObjectInfo(bank->appearance);
|
||||
|
||||
CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
|
||||
|
||||
ui64 totalStrength = 0;
|
||||
ui8 totalChance = 0;
|
||||
for(auto config : bankInfo->getPossibleGuards(bank->cb))
|
||||
{
|
||||
totalStrength += config.second.totalStrength * config.first;
|
||||
totalChance += config.first;
|
||||
}
|
||||
return totalStrength / std::max<ui8>(totalChance, 1); //avoid division by zero
|
||||
|
||||
}
|
||||
|
||||
ui64 FuzzyHelper::evaluateDanger(const int3 & tile, const CGHeroInstance * visitor, bool checkGuards)
|
||||
{
|
||||
auto cb = ai->cb.get();
|
||||
@ -167,30 +148,14 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj)
|
||||
return 0;
|
||||
[[fallthrough]];
|
||||
}
|
||||
case Obj::MONSTER:
|
||||
case Obj::GARRISON:
|
||||
case Obj::GARRISON2:
|
||||
case Obj::CREATURE_GENERATOR1:
|
||||
case Obj::CREATURE_GENERATOR4:
|
||||
case Obj::MINE:
|
||||
case Obj::ABANDONED_MINE:
|
||||
case Obj::PANDORAS_BOX:
|
||||
case Obj::CRYPT: //crypt
|
||||
case Obj::CREATURE_BANK: //crebank
|
||||
case Obj::DRAGON_UTOPIA:
|
||||
case Obj::SHIPWRECK: //shipwreck
|
||||
case Obj::DERELICT_SHIP: //derelict ship
|
||||
default:
|
||||
{
|
||||
const CArmedInstance * a = dynamic_cast<const CArmedInstance *>(obj);
|
||||
return a->getArmyStrength();
|
||||
if (a)
|
||||
return a->getArmyStrength();
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
case Obj::PYRAMID:
|
||||
{
|
||||
return estimateBankDanger(dynamic_cast<const CBank *>(obj));
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -10,12 +10,6 @@
|
||||
#pragma once
|
||||
#include "FuzzyEngines.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class CBank;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
namespace NKAI
|
||||
{
|
||||
|
||||
@ -30,8 +24,6 @@ private:
|
||||
public:
|
||||
FuzzyHelper(const Nullkiller * ai): ai(ai) {}
|
||||
|
||||
ui64 estimateBankDanger(const CBank * bank); //TODO: move to another class?
|
||||
|
||||
ui64 evaluateDanger(const CGObjectInstance * obj);
|
||||
ui64 evaluateDanger(const int3 & tile, const CGHeroInstance * visitor, bool checkGuards = true);
|
||||
};
|
||||
|
@ -128,25 +128,6 @@ int32_t estimateTownIncome(CCallback * cb, const CGObjectInstance * target, cons
|
||||
return booster * (town->hasFort() && town->tempOwner != PlayerColor::NEUTRAL ? booster * 500 : 250);
|
||||
}
|
||||
|
||||
TResources getCreatureBankResources(const CGObjectInstance * target, const CGHeroInstance * hero)
|
||||
{
|
||||
//Fixme: unused variable hero
|
||||
|
||||
auto objectInfo = target->getObjectHandler()->getObjectInfo(target->appearance);
|
||||
CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
|
||||
auto resources = bankInfo->getPossibleResourcesReward();
|
||||
TResources result = TResources();
|
||||
int sum = 0;
|
||||
|
||||
for(auto & reward : resources)
|
||||
{
|
||||
result += reward.data * reward.chance;
|
||||
sum += reward.chance;
|
||||
}
|
||||
|
||||
return sum > 1 ? result / sum : result;
|
||||
}
|
||||
|
||||
int32_t getResourcesGoldReward(const TResources & res)
|
||||
{
|
||||
int32_t result = 0;
|
||||
@ -313,22 +294,13 @@ uint64_t RewardEvaluator::getArmyReward(
|
||||
{
|
||||
case Obj::HILL_FORT:
|
||||
return ai->armyManager->calculateCreaturesUpgrade(army, target, ai->cb->getResourceAmount()).upgradeValue;
|
||||
case Obj::CREATURE_BANK:
|
||||
return getCreatureBankArmyReward(target, hero);
|
||||
case Obj::CREATURE_GENERATOR1:
|
||||
case Obj::CREATURE_GENERATOR2:
|
||||
case Obj::CREATURE_GENERATOR3:
|
||||
case Obj::CREATURE_GENERATOR4:
|
||||
return getDwellingArmyValue(ai->cb.get(), target, checkGold);
|
||||
case Obj::CRYPT:
|
||||
case Obj::SHIPWRECK:
|
||||
case Obj::SHIPWRECK_SURVIVOR:
|
||||
case Obj::WARRIORS_TOMB:
|
||||
return 1000;
|
||||
case Obj::ARTIFACT:
|
||||
return evaluateArtifactArmyValue(dynamic_cast<const CGArtifact *>(target)->storedArtifact->artType);
|
||||
case Obj::DRAGON_UTOPIA:
|
||||
return 10000;
|
||||
case Obj::HERO:
|
||||
return relations == PlayerRelations::ENEMIES
|
||||
? enemyArmyEliminationRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->getArmyStrength()
|
||||
@ -563,13 +535,6 @@ float RewardEvaluator::getStrategicalValue(const CGObjectInstance * target, cons
|
||||
return getResourceRequirementStrength(res);
|
||||
}
|
||||
|
||||
case Obj::CREATURE_BANK:
|
||||
{
|
||||
auto resourceReward = getCreatureBankResources(target, nullptr);
|
||||
|
||||
return getResourceRequirementStrength(resourceReward);
|
||||
}
|
||||
|
||||
case Obj::TOWN:
|
||||
{
|
||||
if(ai->buildAnalyzer->getDevelopmentInfo().empty())
|
||||
@ -728,8 +693,6 @@ float RewardEvaluator::getSkillReward(const CGObjectInstance * target, const CGH
|
||||
case Obj::PANDORAS_BOX:
|
||||
//Can contains experience, spells, or skills (only on custom maps)
|
||||
return 2.5f;
|
||||
case Obj::PYRAMID:
|
||||
return 6.0f;
|
||||
case Obj::HERO:
|
||||
return ai->cb->getPlayerRelations(target->tempOwner, ai->playerID) == PlayerRelations::ENEMIES
|
||||
? enemyHeroEliminationSkillRewardRatio * dynamic_cast<const CGHeroInstance *>(target)->level
|
||||
@ -836,22 +799,6 @@ int32_t RewardEvaluator::getGoldReward(const CGObjectInstance * target, const CG
|
||||
auto * mine = dynamic_cast<const CGMine*>(target);
|
||||
return dailyIncomeMultiplier * (mine->producedResource == GameResID::GOLD ? 1000 : 75);
|
||||
}
|
||||
case Obj::MYSTICAL_GARDEN:
|
||||
case Obj::WINDMILL:
|
||||
return 100;
|
||||
case Obj::CAMPFIRE:
|
||||
return 800;
|
||||
case Obj::WAGON:
|
||||
return 100;
|
||||
case Obj::CREATURE_BANK:
|
||||
return getResourcesGoldReward(getCreatureBankResources(target, hero));
|
||||
case Obj::CRYPT:
|
||||
case Obj::DERELICT_SHIP:
|
||||
return 3000;
|
||||
case Obj::DRAGON_UTOPIA:
|
||||
return 10000;
|
||||
case Obj::SEA_CHEST:
|
||||
return 1500;
|
||||
case Obj::PANDORAS_BOX:
|
||||
return 2500;
|
||||
case Obj::PRISON:
|
||||
|
@ -16,7 +16,6 @@
|
||||
#include "../../lib/UnlockGuard.h"
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/CHeroHandler.h"
|
||||
#include "../../lib/mapObjects/CBank.h"
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
#include "../../lib/mapObjects/CQuest.h"
|
||||
#include "../../lib/mapping/CMapDefines.h"
|
||||
|
@ -219,12 +219,6 @@ float TacticalAdvantageEngine::getTacticalAdvantage(const CArmedInstance * we, c
|
||||
enemyFlyers->setValue(enemyStructure.flyers);
|
||||
enemySpeed->setValue(enemyStructure.maxSpeed);
|
||||
|
||||
bool bank = dynamic_cast<const CBank *>(enemy);
|
||||
if(bank)
|
||||
bankPresent->setValue(1);
|
||||
else
|
||||
bankPresent->setValue(0);
|
||||
|
||||
const CGTownInstance * fort = dynamic_cast<const CGTownInstance *>(enemy);
|
||||
if(fort)
|
||||
castleWalls->setValue(fort->fortLevel());
|
||||
|
@ -16,7 +16,6 @@
|
||||
#include "../../lib/mapObjectConstructors/AObjectTypeHandler.h"
|
||||
#include "../../lib/mapObjectConstructors/CObjectClassesHandler.h"
|
||||
#include "../../lib/mapObjectConstructors/CBankInstanceConstructor.h"
|
||||
#include "../../lib/mapObjects/CBank.h"
|
||||
#include "../../lib/mapObjects/CGCreature.h"
|
||||
#include "../../lib/mapObjects/CGDwelling.h"
|
||||
#include "../../lib/gameState/InfoAboutArmy.h"
|
||||
@ -62,25 +61,6 @@ Goals::TSubgoal FuzzyHelper::chooseSolution(Goals::TGoalVec vec)
|
||||
return result;
|
||||
}
|
||||
|
||||
ui64 FuzzyHelper::estimateBankDanger(const CBank * bank)
|
||||
{
|
||||
//this one is not fuzzy anymore, just calculate weighted average
|
||||
|
||||
auto objectInfo = bank->getObjectHandler()->getObjectInfo(bank->appearance);
|
||||
|
||||
CBankInfo * bankInfo = dynamic_cast<CBankInfo *>(objectInfo.get());
|
||||
|
||||
ui64 totalStrength = 0;
|
||||
ui8 totalChance = 0;
|
||||
for(auto config : bankInfo->getPossibleGuards(bank->cb))
|
||||
{
|
||||
totalStrength += config.second.totalStrength * config.first;
|
||||
totalChance += config.first;
|
||||
}
|
||||
return totalStrength / std::max<ui8>(totalChance, 1); //avoid division by zero
|
||||
|
||||
}
|
||||
|
||||
float FuzzyHelper::evaluate(Goals::VisitTile & g)
|
||||
{
|
||||
if(g.parent)
|
||||
@ -301,32 +281,13 @@ ui64 FuzzyHelper::evaluateDanger(const CGObjectInstance * obj, const VCAI * ai)
|
||||
cb->getTownInfo(obj, iat);
|
||||
return iat.army.getStrength();
|
||||
}
|
||||
case Obj::MONSTER:
|
||||
{
|
||||
//TODO!!!!!!!!
|
||||
const CGCreature * cre = dynamic_cast<const CGCreature *>(obj);
|
||||
return cre->getArmyStrength();
|
||||
}
|
||||
case Obj::CREATURE_GENERATOR1:
|
||||
case Obj::CREATURE_GENERATOR4:
|
||||
{
|
||||
const CGDwelling * d = dynamic_cast<const CGDwelling *>(obj);
|
||||
return d->getArmyStrength();
|
||||
}
|
||||
case Obj::MINE:
|
||||
case Obj::ABANDONED_MINE:
|
||||
default:
|
||||
{
|
||||
const CArmedInstance * a = dynamic_cast<const CArmedInstance *>(obj);
|
||||
return a->getArmyStrength();
|
||||
if (a)
|
||||
return a->getArmyStrength();
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
case Obj::CRYPT: //crypt
|
||||
case Obj::CREATURE_BANK: //crebank
|
||||
case Obj::DRAGON_UTOPIA:
|
||||
case Obj::SHIPWRECK: //shipwreck
|
||||
case Obj::DERELICT_SHIP: //derelict ship
|
||||
case Obj::PYRAMID:
|
||||
return estimateBankDanger(dynamic_cast<const CBank *>(obj));
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -10,12 +10,6 @@
|
||||
#pragma once
|
||||
#include "FuzzyEngines.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class CBank;
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
class DLL_EXPORT FuzzyHelper
|
||||
{
|
||||
public:
|
||||
@ -42,8 +36,6 @@ public:
|
||||
float evaluate(Goals::AbstractGoal & g);
|
||||
void setPriority(Goals::TSubgoal & g);
|
||||
|
||||
ui64 estimateBankDanger(const CBank * bank); //TODO: move to another class?
|
||||
|
||||
Goals::TSubgoal chooseSolution(Goals::TGoalVec vec);
|
||||
//std::shared_ptr<AbstractGoal> chooseSolution (std::vector<std::shared_ptr<AbstractGoal>> & vec);
|
||||
|
||||
|
@ -2750,8 +2750,6 @@ bool isWeeklyRevisitable(const CGObjectInstance * obj)
|
||||
|
||||
if(dynamic_cast<const CGDwelling *>(obj))
|
||||
return true;
|
||||
if(dynamic_cast<const CBank *>(obj)) //banks tend to respawn often in mods
|
||||
return true;
|
||||
|
||||
switch(obj->ID)
|
||||
{
|
||||
|
@ -21,7 +21,11 @@
|
||||
#include "globalLobby/GlobalLobbyClient.h"
|
||||
#include "lobby/CSelectionBase.h"
|
||||
#include "lobby/CLobbyScreen.h"
|
||||
#include "lobby/CBonusSelection.h"
|
||||
#include "windows/InfoWindows.h"
|
||||
#include "media/CMusicHandler.h"
|
||||
#include "media/IVideoPlayer.h"
|
||||
|
||||
|
||||
#include "mainmenu/CMainMenu.h"
|
||||
#include "mainmenu/CPrologEpilogVideo.h"
|
||||
@ -704,7 +708,15 @@ void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared
|
||||
else
|
||||
{
|
||||
CMM->openCampaignScreen(ourCampaign->campaignSet);
|
||||
GH.windows().createAndPushWindow<CHighScoreInputScreen>(true, *campaignScoreCalculator, statistic);
|
||||
if(!ourCampaign->getOutroVideo().empty() && CCS->videoh->open(ourCampaign->getOutroVideo(), false))
|
||||
{
|
||||
CCS->musich->stopMusic();
|
||||
GH.windows().createAndPushWindow<CampaignRimVideo>(ourCampaign->getOutroVideo(), ourCampaign->getVideoRim().empty() ? ImagePath::builtin("INTRORIM") : ourCampaign->getVideoRim(), [campaignScoreCalculator, statistic](){
|
||||
GH.windows().createAndPushWindow<CHighScoreInputScreen>(true, *campaignScoreCalculator, statistic);
|
||||
});
|
||||
}
|
||||
else
|
||||
GH.windows().createAndPushWindow<CHighScoreInputScreen>(true, *campaignScoreCalculator, statistic);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -193,9 +193,8 @@ public:
|
||||
void heroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) override {};
|
||||
void visitCastleObjects(const CGTownInstance * obj, const CGHeroInstance * hero) override {};
|
||||
void stopHeroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) override {};
|
||||
void startBattlePrimary(const CArmedInstance * army1, const CArmedInstance * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool creatureBank = false, const CGTownInstance * town = nullptr) override {}; //use hero=nullptr for no hero
|
||||
void startBattleI(const CArmedInstance * army1, const CArmedInstance * army2, int3 tile, bool creatureBank = false) override {}; //if any of armies is hero, hero will be used
|
||||
void startBattleI(const CArmedInstance * army1, const CArmedInstance * army2, bool creatureBank = false) override {}; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle
|
||||
void startBattle(const CArmedInstance * army1, const CArmedInstance * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, const BattleLayout & layout, const CGTownInstance * town) override {}; //use hero=nullptr for no hero
|
||||
void startBattle(const CArmedInstance * army1, const CArmedInstance * army2) override {}; //if any of armies is hero, hero will be used
|
||||
bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode movementMode, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL) override {return false;};
|
||||
void giveHeroBonus(GiveBonus * bonus) override {};
|
||||
void setMovePoints(SetMovePoints * smp) override {};
|
||||
|
@ -31,6 +31,8 @@
|
||||
#include "gui/WindowHandler.h"
|
||||
#include "widgets/Buttons.h"
|
||||
#include "widgets/TextControls.h"
|
||||
#include "media/CMusicHandler.h"
|
||||
#include "media/IVideoPlayer.h"
|
||||
|
||||
#include "../lib/CConfigHandler.h"
|
||||
#include "../lib/texts/CGeneralTextHandler.h"
|
||||
@ -203,11 +205,19 @@ void ApplyOnLobbyScreenNetPackVisitor::visitLobbyUpdateState(LobbyUpdateState &
|
||||
|
||||
if(!lobby->bonusSel && handler.si->campState && handler.getState() == EClientState::LOBBY_CAMPAIGN)
|
||||
{
|
||||
lobby->bonusSel = std::make_shared<CBonusSelection>();
|
||||
if(!handler.si->campState->conqueredScenarios().size() && !handler.si->campState->getIntroVideo().empty())
|
||||
GH.windows().createAndPushWindow<CampaignIntroVideo>(handler.si->campState->getIntroVideo(), handler.si->campState->getIntroVideoRim().empty() ? ImagePath::builtin("INTRORIM") : handler.si->campState->getIntroVideoRim(), lobby->bonusSel);
|
||||
auto bonusSel = std::make_shared<CBonusSelection>();
|
||||
lobby->bonusSel = bonusSel;
|
||||
if(!handler.si->campState->conqueredScenarios().size() && !handler.si->campState->getIntroVideo().empty() && CCS->videoh->open(handler.si->campState->getIntroVideo(), false))
|
||||
{
|
||||
CCS->musich->stopMusic();
|
||||
GH.windows().createAndPushWindow<CampaignRimVideo>(handler.si->campState->getIntroVideo(), handler.si->campState->getVideoRim().empty() ? ImagePath::builtin("INTRORIM") : handler.si->campState->getVideoRim(), [bonusSel](){
|
||||
if(!CSH->si->campState->getMusic().empty())
|
||||
CCS->musich->playMusic(CSH->si->campState->getMusic(), true, false);
|
||||
GH.windows().pushWindow(bonusSel);
|
||||
});
|
||||
}
|
||||
else
|
||||
GH.windows().pushWindow(lobby->bonusSel);
|
||||
GH.windows().pushWindow(bonusSel);
|
||||
}
|
||||
|
||||
if(lobby->bonusSel)
|
||||
|
@ -59,8 +59,8 @@
|
||||
#include "../../lib/mapObjects/CGHeroInstance.h"
|
||||
|
||||
|
||||
CampaignIntroVideo::CampaignIntroVideo(VideoPath video, ImagePath rim, std::shared_ptr<CBonusSelection> bonusSel)
|
||||
: CWindowObject(BORDERED), bonusSel(bonusSel)
|
||||
CampaignRimVideo::CampaignRimVideo(VideoPath video, ImagePath rim, std::function<void()> closeCb)
|
||||
: CWindowObject(BORDERED), closeCb(closeCb)
|
||||
{
|
||||
OBJECT_CONSTRUCTION;
|
||||
|
||||
@ -70,26 +70,21 @@ CampaignIntroVideo::CampaignIntroVideo(VideoPath video, ImagePath rim, std::shar
|
||||
|
||||
videoPlayer = std::make_shared<VideoWidgetOnce>(Point(80, 186), video, true, [this](){ exit(); });
|
||||
setBackground(rim);
|
||||
|
||||
CCS->musich->stopMusic();
|
||||
}
|
||||
|
||||
void CampaignIntroVideo::exit()
|
||||
void CampaignRimVideo::exit()
|
||||
{
|
||||
close();
|
||||
|
||||
if (!CSH->si->campState->getMusic().empty())
|
||||
CCS->musich->playMusic(CSH->si->campState->getMusic(), true, false);
|
||||
|
||||
GH.windows().pushWindow(bonusSel);
|
||||
if(closeCb)
|
||||
closeCb();
|
||||
}
|
||||
|
||||
void CampaignIntroVideo::clickPressed(const Point & cursorPosition)
|
||||
void CampaignRimVideo::clickPressed(const Point & cursorPosition)
|
||||
{
|
||||
exit();
|
||||
}
|
||||
|
||||
void CampaignIntroVideo::keyPressed(EShortcut key)
|
||||
void CampaignRimVideo::keyPressed(EShortcut key)
|
||||
{
|
||||
exit();
|
||||
}
|
||||
|
@ -33,14 +33,15 @@ class VideoWidgetOnce;
|
||||
class CBonusSelection;
|
||||
|
||||
|
||||
class CampaignIntroVideo : public CWindowObject
|
||||
class CampaignRimVideo : public CWindowObject
|
||||
{
|
||||
std::shared_ptr<VideoWidgetOnce> videoPlayer;
|
||||
std::shared_ptr<CBonusSelection> bonusSel;
|
||||
|
||||
std::function<void()> closeCb;
|
||||
|
||||
void exit();
|
||||
public:
|
||||
CampaignIntroVideo(VideoPath video, ImagePath rim, std::shared_ptr<CBonusSelection> bonusSel);
|
||||
CampaignRimVideo(VideoPath video, ImagePath rim, std::function<void()> closeCb);
|
||||
|
||||
void clickPressed(const Point & cursorPosition) override;
|
||||
void keyPressed(EShortcut key) override;
|
||||
|
@ -1,87 +0,0 @@
|
||||
{
|
||||
"battle_positions":
|
||||
[
|
||||
{
|
||||
"name" : "attackerLoose", // loose formation, attacker
|
||||
"levels": [
|
||||
[ 86 ],
|
||||
[ 35, 137 ],
|
||||
[ 35, 86, 137 ],
|
||||
[ 1, 69, 103, 171 ],
|
||||
[ 1, 35, 86, 137, 171 ],
|
||||
[ 1, 35, 69, 103, 137, 171 ],
|
||||
[ 1, 35, 69, 86, 103, 137, 171 ]
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"name" : "defenderLoose", // loose formation, defender
|
||||
"levels": [
|
||||
[ 100 ],
|
||||
[ 49, 151 ],
|
||||
[ 49, 100, 151 ],
|
||||
[ 15, 83, 117, 185 ],
|
||||
[ 15, 49, 100, 151, 185 ],
|
||||
[ 15, 49, 83, 117, 151, 185 ],
|
||||
[ 15, 49, 83, 100, 117, 151, 185 ]
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"name" : "attackerTight", // tight formation, attacker
|
||||
"levels": [
|
||||
[ 86 ],
|
||||
[ 69, 103 ],
|
||||
[ 69, 86, 103 ],
|
||||
[ 35, 69, 103, 137 ],
|
||||
[ 35, 69, 86, 103, 137 ],
|
||||
[ 1, 35, 69, 103, 137, 171 ],
|
||||
[ 1, 35, 69, 86, 103, 137, 171 ]
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"name" : "defenderTight", // tight formation, defender
|
||||
"levels": [
|
||||
[ 100 ],
|
||||
[ 83, 117 ],
|
||||
[ 83, 100, 117 ],
|
||||
[ 49, 83, 117, 151 ],
|
||||
[ 49, 83, 100, 117, 151 ],
|
||||
[ 15, 49, 83, 117, 151, 185 ],
|
||||
[ 15, 49, 83, 100, 117, 151, 185 ]
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"name" : "attackerCreBank", // creature bank, attacker
|
||||
"levels": [
|
||||
[ 57 ],
|
||||
[ 57, 61 ],
|
||||
[ 57, 61, 90 ],
|
||||
[ 57, 61, 90, 93 ],
|
||||
[ 57, 61, 90, 93, 96 ],
|
||||
[ 57, 61, 90, 93, 96, 125 ],
|
||||
[ 57, 61, 90, 93, 96, 125, 129 ]
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"name" : "defenderCreBank", // creature bank, defender
|
||||
"levels": [
|
||||
[ 15 ],
|
||||
[ 15, 185 ],
|
||||
[ 15, 185, 172 ],
|
||||
[ 15, 185, 172, 2 ],
|
||||
[ 15, 185, 172, 2, 100 ],
|
||||
[ 15, 185, 172, 2, 100, 86 ],
|
||||
[ 15, 185, 172, 2, 100, 86, 8 ]
|
||||
]
|
||||
}
|
||||
],
|
||||
"commanderPositions":
|
||||
{
|
||||
"field" : [88, 98], //attacker/defender
|
||||
"creBank" : [95, 8] //not expecting defendig hero at bank, but hell knows
|
||||
}
|
||||
}
|
@ -251,7 +251,7 @@
|
||||
|
||||
// Section 4 - buildings that now have dedicated mechanics
|
||||
"ballistaYard": {
|
||||
"blacksmith" : "ballista"
|
||||
"warMachine" : "ballista"
|
||||
},
|
||||
|
||||
"thievesGuild" : {
|
||||
|
@ -1,4 +1,11 @@
|
||||
{
|
||||
"DATA/GOOD3" : { // RoE - "Song for the Father"
|
||||
"outroVideo": "Endgame"
|
||||
},
|
||||
"DATA/AB" : { // AB Intro
|
||||
"introVideo": "H3X1intr",
|
||||
"videoRim": "IntroRm2"
|
||||
},
|
||||
"MAPS/HC1_MAIN" : { // Heroes Chronicles 1
|
||||
"regions":
|
||||
{
|
||||
@ -29,7 +36,7 @@
|
||||
{ "voiceProlog": "chronicles_1/H3X2BBF", "voiceEpilog": "chronicles_1/N1C_D" }
|
||||
],
|
||||
"loadingBackground": "chronicles_1/LoadBar",
|
||||
"introVideoRim": "chronicles_1/INTRORIM",
|
||||
"videoRim": "chronicles_1/INTRORIM",
|
||||
"introVideo": "chronicles_1/Intro"
|
||||
},
|
||||
"MAPS/HC2_MAIN" : { // Heroes Chronicles 2
|
||||
@ -62,7 +69,7 @@
|
||||
{ "voiceProlog": "chronicles_2/G1A", "voiceEpilog": "chronicles_2/S1C" }
|
||||
],
|
||||
"loadingBackground": "chronicles_2/LoadBar",
|
||||
"introVideoRim": "chronicles_2/INTRORIM",
|
||||
"videoRim": "chronicles_2/INTRORIM",
|
||||
"introVideo": "chronicles_2/Intro"
|
||||
},
|
||||
"MAPS/HC3_MAIN" : { // Heroes Chronicles 3
|
||||
@ -95,7 +102,7 @@
|
||||
{ "voiceProlog": "chronicles_3/G3B", "voiceEpilog": "chronicles_3/ABVOFL2" }
|
||||
],
|
||||
"loadingBackground": "chronicles_3/LoadBar",
|
||||
"introVideoRim": "chronicles_3/INTRORIM",
|
||||
"videoRim": "chronicles_3/INTRORIM",
|
||||
"introVideo": "chronicles_3/Intro"
|
||||
},
|
||||
"MAPS/HC4_MAIN" : { // Heroes Chronicles 4
|
||||
@ -128,7 +135,7 @@
|
||||
{ "voiceProlog": "chronicles_4/H3X2NBD", "voiceEpilog": "chronicles_4/S1C" }
|
||||
],
|
||||
"loadingBackground": "chronicles_4/LoadBar",
|
||||
"introVideoRim": "chronicles_4/INTRORIM",
|
||||
"videoRim": "chronicles_4/INTRORIM",
|
||||
"introVideo": "chronicles_4/Intro"
|
||||
},
|
||||
"MAPS/HC5_MAIN" : { // Heroes Chronicles 5
|
||||
@ -155,7 +162,7 @@
|
||||
{ "voiceProlog": "chronicles_5/H3X2UAH", "voiceEpilog": "chronicles_5/N1C_D" }
|
||||
],
|
||||
"loadingBackground": "chronicles_5/LoadBar",
|
||||
"introVideoRim": "chronicles_5/INTRORIM",
|
||||
"videoRim": "chronicles_5/INTRORIM",
|
||||
"introVideo": "chronicles_5/Intro"
|
||||
},
|
||||
"MAPS/HC6_MAIN" : { // Heroes Chronicles 6
|
||||
@ -182,7 +189,7 @@
|
||||
{ "voiceProlog": "chronicles_6/ABVOAB5", "voiceEpilog": "chronicles_6/ABVODB2" }
|
||||
],
|
||||
"loadingBackground": "chronicles_6/LoadBar",
|
||||
"introVideoRim": "chronicles_6/INTRORIM",
|
||||
"videoRim": "chronicles_6/INTRORIM",
|
||||
"introVideo": "chronicles_6/Intro"
|
||||
},
|
||||
"MAPS/HC7_MAIN" : { // Heroes Chronicles 7
|
||||
@ -215,7 +222,7 @@
|
||||
{ "voiceProlog": "chronicles_7/ABVOFW4", "voiceEpilog": "chronicles_7/ABVOAB1" }
|
||||
],
|
||||
"loadingBackground": "chronicles_7/LoadBar",
|
||||
"introVideoRim": "chronicles_7/INTRORIM",
|
||||
"videoRim": "chronicles_7/INTRORIM",
|
||||
"introVideo": "chronicles_7/Intro5"
|
||||
},
|
||||
"MAPS/HC8_MAIN" : { // Heroes Chronicles 8
|
||||
@ -248,7 +255,7 @@
|
||||
{ "voiceProlog": "chronicles_8/H3X2ELE", "voiceEpilog": "chronicles_8/ABVOAB7" }
|
||||
],
|
||||
"loadingBackground": "chronicles_8/LoadBar",
|
||||
"introVideoRim": "chronicles_8/INTRORIM",
|
||||
"videoRim": "chronicles_8/INTRORIM",
|
||||
"introVideo": "chronicles_8/Intro6"
|
||||
}
|
||||
}
|
||||
|
@ -57,6 +57,7 @@
|
||||
"config/objects/magicWell.json",
|
||||
"config/objects/moddables.json",
|
||||
"config/objects/observatory.json",
|
||||
"config/objects/pyramid.json",
|
||||
"config/objects/rewardableBonusing.json",
|
||||
"config/objects/rewardableOncePerHero.json",
|
||||
"config/objects/rewardableOncePerWeek.json",
|
||||
@ -342,8 +343,77 @@
|
||||
// limit of damage reduction that can be achieved by overpowering defense points
|
||||
"defensePointDamageFactorCap": 0.7,
|
||||
// If set to true, double-wide creatures will trigger obstacle effect when moving one tile forward or backwards
|
||||
"oneHexTriggersObstacles": false
|
||||
},
|
||||
"oneHexTriggersObstacles": false,
|
||||
|
||||
// Positions of units on start of the combat
|
||||
// If battle does not defines specific configuration, 'default' configuration will be used
|
||||
// Configuration must define either 'attackerUnits' list of 7 elements or both 'attackerUnitsLoose' and 'attackerUnitsTight' lists of 7 elements, 1..7 elements each
|
||||
// Similarly, for defender configuration must have either 'defenderUnits' or both 'defenderUnitsLoose' and 'defenderUnitsTight'
|
||||
"layouts" : {
|
||||
"default" : {
|
||||
"tacticsAllowed" : true,
|
||||
"obstaclesAllowed" : true,
|
||||
"attackerCommander" : 88,
|
||||
"defenderCommander" : 98,
|
||||
"attackerWarMachines" : [ 52, 18, 154, 120 ],
|
||||
"defenderWarMachines" : [ 66, 32, 168, 134 ],
|
||||
"attackerUnitsLoose": [
|
||||
[ 86 ],
|
||||
[ 35, 137 ],
|
||||
[ 35, 86, 137 ],
|
||||
[ 1, 69, 103, 171 ],
|
||||
[ 1, 35, 86, 137, 171 ],
|
||||
[ 1, 35, 69, 103, 137, 171 ],
|
||||
[ 1, 35, 69, 86, 103, 137, 171 ]
|
||||
],
|
||||
"defenderUnitsLoose": [
|
||||
[ 100 ],
|
||||
[ 49, 151 ],
|
||||
[ 49, 100, 151 ],
|
||||
[ 15, 83, 117, 185 ],
|
||||
[ 15, 49, 100, 151, 185 ],
|
||||
[ 15, 49, 83, 117, 151, 185 ],
|
||||
[ 15, 49, 83, 100, 117, 151, 185 ]
|
||||
],
|
||||
"attackerUnitsTight": [
|
||||
[ 86 ],
|
||||
[ 69, 103 ],
|
||||
[ 69, 86, 103 ],
|
||||
[ 35, 69, 103, 137 ],
|
||||
[ 35, 69, 86, 103, 137 ],
|
||||
[ 1, 35, 69, 103, 137, 171 ],
|
||||
[ 1, 35, 69, 86, 103, 137, 171 ]
|
||||
],
|
||||
"defenderUnitsTight": [
|
||||
[ 100 ],
|
||||
[ 83, 117 ],
|
||||
[ 83, 100, 117 ],
|
||||
[ 49, 83, 117, 151 ],
|
||||
[ 49, 83, 100, 117, 151 ],
|
||||
[ 15, 49, 83, 117, 151, 185 ],
|
||||
[ 15, 49, 83, 100, 117, 151, 185 ]
|
||||
]
|
||||
},
|
||||
// Configuration for creature banks with single-tile enemies
|
||||
"creatureBankNarrow" : {
|
||||
"tacticsAllowed" : false,
|
||||
"obstaclesAllowed" : false,
|
||||
"attackerCommander" : 95,
|
||||
"defenderCommander" : 8,
|
||||
"attackerUnits": [ 57, 61, 90, 93, 96, 125, 129 ],
|
||||
"defenderUnits": [ 15, 185, 172, 2, 100, 87, 8 ]
|
||||
},
|
||||
// Configuration for creature banks with double-wide enemies
|
||||
"creatureBankWide" : {
|
||||
"tacticsAllowed" : false,
|
||||
"obstaclesAllowed" : false,
|
||||
"attackerCommander" : 95,
|
||||
"defenderCommander" : 8,
|
||||
"attackerUnits": [ 57, 61, 90, 93, 96, 125, 129 ],
|
||||
"defenderUnits": [ 15, 185, 171, 1, 100, 86, 8 ]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"creatures":
|
||||
{
|
||||
|
File diff suppressed because it is too large
Load Diff
76
config/objects/pyramid.json
Normal file
76
config/objects/pyramid.json
Normal file
@ -0,0 +1,76 @@
|
||||
{
|
||||
"pyramid" : {
|
||||
"index" :63,
|
||||
"handler" : "configurable",
|
||||
"base" : {
|
||||
"sounds" : {
|
||||
"visit" : ["MYSTERY"]
|
||||
}
|
||||
},
|
||||
"types" : {
|
||||
"pyramid" : {
|
||||
"index" : 0,
|
||||
|
||||
"name" : "Pyramid",
|
||||
"aiValue" : 8000,
|
||||
"rmg" : {
|
||||
"value" : 5000,
|
||||
"rarity" : 20
|
||||
},
|
||||
|
||||
"variables" : {
|
||||
"spell" : {
|
||||
"gainedSpell" : {
|
||||
"level": 5
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"onGuardedMessage" : 105,
|
||||
"visitMode" : "once",
|
||||
"selectMode" : "selectFirst",
|
||||
"onVisited" : [
|
||||
{
|
||||
"message" : 107,
|
||||
"bonuses" : [ { "type" : "LUCK", "val" : -2, "duration" : "ONE_BATTLE", "description" : 70 } ]
|
||||
}
|
||||
],
|
||||
"guardsLayout" : "default",
|
||||
"rewards" : [
|
||||
{
|
||||
"limiter" : {
|
||||
"canLearnSpells" : [
|
||||
"@gainedSpell"
|
||||
]
|
||||
},
|
||||
"spells" : [
|
||||
"@gainedSpell"
|
||||
],
|
||||
"message" : [ 106, "%s." ], // Upon defeating monsters, you learn new spell
|
||||
"guards" : [
|
||||
{ "amount" : 40, "type" : "goldGolem" },
|
||||
{ "amount" : 10, "type" : "diamondGolem" },
|
||||
{ "amount" : 10, "type" : "diamondGolem" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"onEmpty" : [
|
||||
{
|
||||
"limiter" : {
|
||||
"artifacts" : [
|
||||
{
|
||||
"type" : "spellBook"
|
||||
}
|
||||
]
|
||||
},
|
||||
"message" : [ 106, "%s.", 108 ] // No Wisdom
|
||||
},
|
||||
{
|
||||
"message" : [ 106, "%s.", 109 ] // No spellbook
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -17,7 +17,13 @@
|
||||
"type" : "number"
|
||||
},
|
||||
"handler" : {
|
||||
"type" : "string"
|
||||
"type" : "string",
|
||||
"enum" : [
|
||||
"configurable", "dwelling", "hero", "town", "boat", "market", "hillFort", "shipyard", "monster", "resource", "static", "randomArtifact",
|
||||
"randomHero", "randomResource", "randomTown", "randomMonster", "randomDwelling", "generic", "artifact", "borderGate", "borderGuard", "denOfThieves",
|
||||
"event", "garrison", "heroPlaceholder", "keymaster", "lighthouse", "magi", "mine", "obelisk", "pandora", "prison", "questGuard", "seerHut", "sign",
|
||||
"siren", "monolith", "subterraneanGate", "whirlpool", "terrain"
|
||||
]
|
||||
},
|
||||
"base" : {
|
||||
"type" : "object"
|
||||
|
@ -98,6 +98,9 @@
|
||||
"max" : { "type" : "number", "exclusiveMinimum" : 0, "maximum" : 100 }
|
||||
}
|
||||
},
|
||||
|
||||
"guards" : { "$ref" : "#/definitions/identifierWithValueList" },
|
||||
|
||||
"limiter" : { "$ref" : "#/definitions/limiter" },
|
||||
"message" : { "$ref" : "#/definitions/message" },
|
||||
"description" : { "$ref" : "#/definitions/message" },
|
||||
@ -256,6 +259,9 @@
|
||||
},
|
||||
},
|
||||
|
||||
"onGuardedMessage" : {
|
||||
"$ref" : "#/definitions/message"
|
||||
},
|
||||
"onSelectMessage" : {
|
||||
"$ref" : "#/definitions/message"
|
||||
},
|
||||
@ -287,15 +293,21 @@
|
||||
"type" : "boolean"
|
||||
},
|
||||
|
||||
"coastVisitable": {
|
||||
"type" : "boolean"
|
||||
},
|
||||
|
||||
"visitMode": {
|
||||
"enum" : [ "unlimited", "once", "hero", "bonus", "limiter", "player" ],
|
||||
"type" : "string"
|
||||
},
|
||||
|
||||
"visitLimiter": {
|
||||
"type" : "object"
|
||||
"guardsLayout": {
|
||||
"type" : "string"
|
||||
},
|
||||
|
||||
"visitLimiter": { "$ref" : "#/definitions/limiter" },
|
||||
|
||||
"selectMode": {
|
||||
"enum" : [ "selectFirst", "selectPlayer", "selectRandom", "selectAll" ],
|
||||
"type" : "string"
|
||||
|
@ -54,7 +54,8 @@ In header are parameters describing campaign properties
|
||||
- `"allowDifficultySelection"` is a boolean field (`true`/`false`) which allows or disallows to choose difficulty before scenario start
|
||||
- `"loadingBackground"` is for setting a different loading screen background
|
||||
- `"introVideo"` is for defining an optional intro video
|
||||
- `"introVideoRim"` is for the Rim around the optional video (default is INTRORIM)
|
||||
- `"outroVideo"` is for defining an optional outro video
|
||||
- `"videoRim"` is for the Rim around the optional video (default is INTRORIM)
|
||||
|
||||
## Scenario description
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
# Creature Format
|
||||
|
||||
This page tells you what you need to do to make your creature work. For help, tips and advices, read the [creature help](Creature_Help.md).
|
||||
|
||||
## Required data
|
||||
|
||||
In order to make functional creature you also need:
|
||||
|
154
docs/modders/Entities_Format/Creature_Help.md
Normal file
154
docs/modders/Entities_Format/Creature_Help.md
Normal file
@ -0,0 +1,154 @@
|
||||
# Creature Help
|
||||
|
||||
This page helps you to create a creature (i.e. a unit that fights in a battle) for a creature mod or inside a bigger mod like a [faction mod](Faction_Help.md).
|
||||
|
||||
## Utilities
|
||||
|
||||
You need to download the two utilities [`DefPreview`](https://sourceforge.net/projects/grayface-misc/files/DefPreview-1.2.1/) and [`H3DefTool`](https://sourceforge.net/projects/grayface-misc/files/H3DefTool-3.4.2/) from the internet:
|
||||
- `DefPreview` converts a `.def` file to `.bmp` images
|
||||
- `H3DefTool` converts `.bmp` images to a `.def` file
|
||||
|
||||
But you can create a configuration that directly reads your image files. Most of the existing mods are coded with `.def` files but direct images are recommended.
|
||||
|
||||
## Make a playable creature
|
||||
|
||||
First of all, retrieve an existing creature and be sure you can clone it and make it work independently without any new content. If it already fails, don't waste your time to draw the new animation. It should work first.
|
||||
|
||||
## Battle render
|
||||
|
||||
The sun is always at zenith, so the shadow is always behind. The reason is that the creature render may be mirrored. There was no strong rules in the original game but usually, the shadow is twice less higher than the creature.
|
||||
|
||||
We don't know the right elevation angle for the view.
|
||||
|
||||
### 3D render
|
||||
You can render your creature using a 3D software like _Blender_. You can start with those free-licenced rigged 3D models:
|
||||
- [Fantasy-bandit](https://www.cgtrader.com/free-3d-models/character/man/fantasy-bandit)
|
||||
- [Monster-4](https://www.cgtrader.com/free-3d-models/character/fantasy-character/monster-4-f5757b92-dc9c-4f5e-ad0d-593203d14fe2)
|
||||
- [Crypt-fiend-modular-character](https://www.cgtrader.com/free-3d-models/character/fantasy-character/crypt-fiend-modular-character-demo-scene)
|
||||
- [Solus-the-knight](https://www.cgtrader.com/free-3d-models/character/man/solus-the-knight-low-poly-character)
|
||||
- [Ancient-earth-golem](https://www.cgtrader.com/free-3d-models/character/fantasy-character/ancient-earth-golem-v2)
|
||||
- [Shadow-golem-elemental](https://www.cgtrader.com/free-3d-models/character/fantasy-character/shadow-golem-elemental)
|
||||
- [Earth-golem-elemental](https://www.cgtrader.com/free-3d-models/character/fantasy-character/earth-golem-elemental)
|
||||
- [Kong-2021-rig](https://www.cgtrader.com/free-3d-models/character/sci-fi-character/kong-2021-rig)
|
||||
- [Shani](https://www.cgtrader.com/free-3d-models/character/woman/shani-3d-character)
|
||||
|
||||
You can also create your 3D model from a single image:
|
||||
- _Stable Fast 3D_: https://huggingface.co/spaces/stabilityai/stable-fast-3d
|
||||
- _Unique3D_: https://huggingface.co/spaces/abreza/Unique3D
|
||||
|
||||
To use it in _Blender_, create a `.blend` project and import the file. To render the texture:
|
||||
1. Add a _Principled BSDF_ material to the object
|
||||
1. Create a _Color Attribute_ in the _Shader Editor_ view
|
||||
1. Link the Color output of the _Color Attribute_ to the _Base color_ input of the _Principled BSDF_
|
||||
|
||||
You can improve details by cropping the source image on a detail and generate a model for this detail. Once both imported in _Blender_, melt them together.
|
||||
|
||||
Render the images without background by selecting png RVBA and disabling background (_Film_ -> _Filter_ -> _Transparent_). It avoids the creatures to have an ugly dark border. Then, to correctly separate the creature from the cyan area, in _GIMP_, apply the threeshold on the transparency by clicking on _Layer_ -> _Transparency_ -> _Alpha threeshold_.
|
||||
|
||||
The global FPS of the game is 10 f/s but you can render at a higher level and configure it in the `.json` files. We are not in the 1990's.
|
||||
|
||||
### IA render
|
||||
|
||||
You can also use an AI like _Flux_ to generate the main creature representation: https://huggingface.co/spaces/multimodalart/FLUX.1-merged
|
||||
|
||||
Then you can add random animations for idle states with _SVD_: https://huggingface.co/spaces/xi0v/Stable-Video-Diffusion-Img2Vid
|
||||
|
||||
Most of the time, the creatures do not move more than one pixel in an idle animation. The reason may be to avoid too much animation on screen and make the transition with the other animations always seamless. Use poses with _ControlNet_ or _OpenPose_. For specific animations, I recommend to use _Cinemo_ because it adds a description prompt but the resolution is smaller: https://huggingface.co/spaces/maxin-cn/Cinemo
|
||||
|
||||
Make animations seamless from one to another. To do this, you can draw the first and the last images with a prompt with _ToonCrafter_: https://huggingface.co/spaces/ChristianHappy/tooncrafter
|
||||
|
||||
Most of the time, you need to increase the resolution or the quality of your template image, so use _SUPIR_: https://huggingface.co/spaces/Fabrice-TIERCELIN/SUPIR
|
||||
|
||||
## Battle sound effect
|
||||
|
||||
To create the audio effects, I recommend to use _Tango 2_: https://huggingface.co/spaces/declare-lab/tango2
|
||||
|
||||
The quality is better than _Stable Audio_.
|
||||
|
||||
## Map render
|
||||
|
||||
We don't know the right elevation angle for the view but 45° elevation seems to be a good choice. For the sunlight direction, I would say 45° elevation and 45° azimut.
|
||||
|
||||
The map creatures are not rendered on the map with vanishing points but in isometric. You can [get an orthogonal render in Blender](https://blender.stackexchange.com/a/135384/2768). If you are creating a creature and its updated version, most of the time, the both creatures are not oriented to the same side on the map. I think that the animation on the map is usually the _Mouse Over_ animation on battle.
|
||||
|
||||
You can see that the view angle is higher than on a battle. To change the angle from a battle sprite, you can use _Zero 1-to-3_: https://huggingface.co/spaces/cvlab/zero123-live
|
||||
|
||||
You can get higher resolution using this Video AI that can control the motion of the camera: https://huggingface.co/spaces/TencentARC/MotionCtrl_SVD
|
||||
|
||||
If you have a 3D software, you can get better quality by converting your image into 3D model and then render it from another angle using _Stable Fast 3D_: https://huggingface.co/spaces/stabilityai/stable-fast-3d
|
||||
|
||||
Follow this comment to retrieve the color: https://huggingface.co/stabilityai/TripoSR/discussions/1#65e8a8e5e214f37d85dad366
|
||||
|
||||
### Shadow render
|
||||
|
||||
There are no strong rules in the original game about the angle of the shadows on the map. Different buildings have inconsistent shadows. To draw the shadow, I recommend the following technique:
|
||||
|
||||
Let's consider that the object is a vertical cone:
|
||||
| | | | | | | | | | |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | ⬛ | 🟦 | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | ⬛ | 🟦 | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | ⬛ | ⬛ | ⬛ | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | ⬛ | ⬛ | ⬛ | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | ⬛ | ⬛ | ⬛ | ⬛ | ⬛ |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | ⬛ | ⬛ | ⬛ | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 |
|
||||
|
||||
Locate the top and its projection to the ground:
|
||||
| | | | | | | | | | |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟥 | 🟦 | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | ⬛ | 🟦 | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | ⬛ | ⬛ | ⬛ | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | ⬛ | ⬛ | ⬛ | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | ⬛ | ⬛ | 🟥 | ⬛ | ⬛ |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | ⬛ | ⬛ | ⬛ | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 |
|
||||
|
||||
Then draw a rectangle triangle on the left:
|
||||
| | | | | | | | | | |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 💟 | 🟦 | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 💟 | ⬛ | 🟦 | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 💟 | ⬛ | ⬛ | ⬛ | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 💟 | ⬛ | ⬛ | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | ⬛ | ⬛ | 💟 | ⬛ | ⬛ |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | ⬛ | ⬛ | ⬛ | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 |
|
||||
|
||||
The square top is the projection of the shadow of the top of the cone:
|
||||
| | | | | | | | | | |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | ⬛ | 🟦 | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | ⬛ | 🟦 | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 💟 | ⬛ | ⬛ | ⬛ | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | ⬛ | ⬛ | ⬛ | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | ⬛ | ⬛ | ⬛ | ⬛ | ⬛ |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | ⬛ | ⬛ | ⬛ | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 |
|
||||
|
||||
Then you can draw the rest of the shadow:
|
||||
| | | | | | | | | | |
|
||||
|---|---|---|---|---|---|---|---|---|---|
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | ⬛ | 🟦 | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | ⬛ | 🟦 | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 💟 | ⬛ | ⬛ | ⬛ | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 💟 | 🟪 | ⬛ | ⬛ | ⬛ | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 💟 | ⬛ | ⬛ | ⬛ | ⬛ | ⬛ |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | ⬛ | ⬛ | ⬛ | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 |
|
||||
| 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 | 🟦 |
|
@ -1,8 +1,10 @@
|
||||
# Faction Format
|
||||
|
||||
This page tells you what you need to do to make your faction work. For help, tips and advices, read the [faction help](Faction_Help.md).
|
||||
|
||||
## Required data
|
||||
|
||||
In order to make functional town you also need:
|
||||
In order to make functional town, you also need:
|
||||
|
||||
### Images
|
||||
|
||||
|
92
docs/modders/Entities_Format/Faction_Help.md
Normal file
92
docs/modders/Entities_Format/Faction_Help.md
Normal file
@ -0,0 +1,92 @@
|
||||
# Faction Help
|
||||
|
||||
This page helps you to create from scratch a VCMI mod that adds a new faction. The faction mod structure is described [here](Faction_Format.md).
|
||||
|
||||
## Questioning the faction creation
|
||||
Before creating a faction, be aware that creating a faction mod is lots of work. You can start [creating creatures](Creature_Help.md) in a creature mod that can be converted into a faction mod after. This way, you are sure to release something. The smallest contribution is a hero portrait that you can suggest on an existing mod. You can also restore the former version of the [Ruins faction](https://github.com/vcmi-mods/ruins-town/tree/1bea30a1d915770e2fd0f95d158030815ff462cd). You would only have to remake the similar parts to the new version.
|
||||
|
||||
## Make a playable faction mod
|
||||
Before creating your content, retrieve the content of an existing faction mod like [Highlands town](https://github.com/vcmi-mods/highlands-town). To download the project, click on the _Code_ button and click on _Download ZIP_. The first thing to do is to change all the faction identifiers in the files following the [faction format](Faction_Format.md) and manage to play with the faction and the original without any conflict. To play to a faction, you have to add all the files in your _Mods_ folder. When it works, you will be able to modify the content step by step.
|
||||
|
||||
Keep in mind that the most important part of a faction mod, above the animations, the graphisms and the musics, is the concept because if you have to change it, you have to change everything else. All the remaining content can be improved by the community.
|
||||
|
||||
## Town screen
|
||||
### Background
|
||||
Beware to direct all the shadows to the same direction. The easiest way to create the background is to use a text-to-image AI. The free most powerful AI at the moment is _Flux_ available here: https://huggingface.co/spaces/multimodalart/FLUX.1-merged
|
||||
|
||||
In the _Advanced Options_, set the width to 800px and set the height to 374px.
|
||||
|
||||
### Buildings
|
||||
To render a building upon the background, I recommend to use an inpainting AI like _BRIA Inpaint_: https://huggingface.co/spaces/briaai/BRIA-2.3-Inpainting
|
||||
|
||||
The idea is to select the area where you want to add the building. As a prompt, describe the new building. The advantage is a perfect match between the background and the building. Keep in mind that to correctly integrate a building image, it must contain the image of the background on its edges. It simulates the semi-transparency.
|
||||
|
||||
You can also animate the building or the background using _Stable Video Diffusion_: https://huggingface.co/spaces/multimodalart/stable-video-diffusion
|
||||
|
||||
## Map dwellings
|
||||
You may want to get the same render as in the town, so you have to change the angle and the shadows. If you handle a 3D model software, you can start with _Stable Fast 3D_: https://huggingface.co/spaces/stabilityai/stable-fast-3d
|
||||
|
||||
The map dwellings are not rendered on the map with vanishing points but in isometric. You can [get an orthogonal render in Blender](https://blender.stackexchange.com/a/135384/2768).
|
||||
|
||||
Without 3D, you can use _Zero 1-to-3_: https://huggingface.co/spaces/cvlab/zero123-live
|
||||
|
||||
You can get higher resolution using this Video AI that can control the motion of the camera: https://huggingface.co/spaces/TencentARC/MotionCtrl_SVD
|
||||
|
||||
The buildings on the map are more satured than on town screen. If you have to reduce the size of an image, do not use interpolation (LANCZOS, Bilinear...) to get more details, not a blurred image. If you need to increase the resolution or the quality of your template image, use _SUPIR_: https://huggingface.co/spaces/Fabrice-TIERCELIN/SUPIR
|
||||
|
||||
## Map buildings
|
||||
The AIs badly understand the sun direction and the perspective angles. To generate the buildings on the adventure map:
|
||||
|
||||
1. Open the HOMM3 map editor
|
||||
1. Put items all around a big empty area
|
||||
1. Make a screenshot
|
||||
1. Go on an AI like _BRIA Inpaint_: https://huggingface.co/spaces/briaai/BRIA-2.3-Inpainting
|
||||
1. Inpaint the (big) empty middle with the brush
|
||||
1. Use a prompt like: `A dark house, at the center of the image, map, isometric, parallel perspective, sunlight from the bottom right`
|
||||
|
||||
## Music
|
||||
Here are unused available themes:
|
||||
|
||||
* [Synthetic Horizon](https://github.com/Fabrice-TIERCELIN/forge/raw/theme/content/music/factions/theme.ogg)
|
||||
1. Prompt: `Dystopy, Cinematic classical, Science fiction, 160 bpm, Best quality, Futuristic`
|
||||
1. Initially created for: _Forge town_
|
||||
|
||||
* [Quantum Overture](https://github.com/Fabrice-TIERCELIN/asylum-town/raw/theme/asylum-town/content/Music/factions/AsylumTown.ogg)
|
||||
1. Prompt: `Clef shifting, Fantasy, Mystical, Overworldly, Cinematic classical`
|
||||
1. Initially created for: _Asylum town_
|
||||
|
||||
* [Warrior s March](https://github.com/vcmi-mods/ruins-town/assets/20668759/964f27de-6feb-4ef6-9d25-455f52938cef)
|
||||
1. Prompt: `Powerful percussions, Drums, Battle Anthem, Rythm, Warrior, 160 bpm, Celtic, New age, Instrumental, Accoustic, Medieval`
|
||||
1. Initially created for: _Ruins town_
|
||||
|
||||
* [Clan of Echoes](https://github.com/Fabrice-TIERCELIN/ruins-town/raw/theme/ruins-town/content/music/ruins.ogg)
|
||||
1. Prompt: `new age, medieval, celtic, warrior, battle, soundtrack, accoustic, drums, rythm`
|
||||
1. Initially created for: _Ruins town_
|
||||
|
||||
* [Enchanted Reverie](https://github.com/Fabrice-TIERCELIN/grove/raw/theme/Grove/content/Music/factions/GroveTown.ogg)
|
||||
1. Prompt: `Classical music, Soundtrack, Score, Instrumental, 160 bpm, ((((fantasy)))), mystic`
|
||||
1. Initially created for: _Grove town_
|
||||
|
||||
* [World Discovery](https://github.com/vcmi-mods/asylum-town/assets/20668759/34438523-8a44-44ca-b493-127501b474a6)
|
||||
1. Prompt: `Clef shifting, fantasy, mystical, overworldly, Cinematic classical`
|
||||
1. Initially created for: _Asylum town_
|
||||
|
||||
* [Enchanted Ballad](https://github.com/vcmi-mods/fairy-town/assets/20668759/619e6e33-d940-4899-8c76-9c1e8d3d20aa)
|
||||
1. Prompt: `Females vocalize, Cinematic classical, Harp, Fairy tale, Princess, 160 bpm`
|
||||
1. Initially created for: _Fairy town_
|
||||
|
||||
* [Baroque Resurgence](https://github.com/Fabrice-TIERCELIN/courtyard_proposal/raw/theme/Courtyard/Content/music/factions/courtyard/CourtTown.ogg)
|
||||
1. Prompt: `Baroque, Instrumental, 160 bpm, Cinematic classical, Best quality`
|
||||
1. Initially created for: _Courtyard town_
|
||||
|
||||
* [Harvest Parade](https://github.com/Fabrice-TIERCELIN/greenhouse-town/raw/theme/Greenhouse/content/Music/town.ogg)
|
||||
1. Prompt: `Marching band, Best quality, Happy, Vegetables`
|
||||
1. Initially created for: _Green town_
|
||||
|
||||
Those themes have been generated using _[Udio](https://udio.com)_.
|
||||
|
||||
## Screenshots
|
||||
Most of the time, the first screenshot is the townscreen because it's the most specific content.
|
||||
|
||||
## Recycle
|
||||
Some mods contain neutral heroes or creatures. You can integrate them in your faction mod. Don't forget to remove the content from the original mod.
|
@ -14,14 +14,6 @@ Full object consists from 3 parts:
|
||||
generated by the game. When new object is created its starting
|
||||
appearance will be copied from template
|
||||
|
||||
## Object types
|
||||
|
||||
- [Rewardable](Map_Objects/Rewardable.md) - Visitable object which grants all kinds of rewards (gold, experience, Bonuses etc...)
|
||||
- [Creature Bank](Map_Objects/Creature_Bank.md) - Object that grants award on defeating guardians
|
||||
- [Dwelling](Map_Objects/Dwelling.md) - Object that allows recruitments of units outside of towns
|
||||
- [Market](Map_Objects/Market.md) - Trading resources, artifacts, creatures and such
|
||||
- [Boat](Map_Objects/Boat.md) - Object to move across different terrains, such as water
|
||||
|
||||
## Object group format
|
||||
|
||||
``` javascript
|
||||
@ -33,7 +25,8 @@ Full object consists from 3 parts:
|
||||
// human readable name, localized
|
||||
"name": "My cool object",
|
||||
|
||||
//defines C++/script class name that handles behavior of this object
|
||||
// defines C++ class name that handles behavior of this object
|
||||
// see Object Types section below for possible values
|
||||
"handler" : "mine",
|
||||
|
||||
// default values, will be merged with each type during loading
|
||||
@ -46,6 +39,61 @@ Full object consists from 3 parts:
|
||||
}
|
||||
```
|
||||
|
||||
## Object types
|
||||
|
||||
### Moddable types
|
||||
These are object types that are available for modding and have configurable properties
|
||||
|
||||
- `configurable` - see [Rewardable](Map_Objects/Rewardable.md). Visitable object which grants all kinds of rewards (gold, experience, Bonuses etc...)
|
||||
- `bank` - see [Creature Bank](Map_Objects/Creature_Bank.md). Object that grants award on defeating guardians. Deprectated in favor of [Rewardable](Map_Objects/Rewardable.md)
|
||||
- `dwelling` - see [Dwelling](Map_Objects/Dwelling.md). Object that allows recruitments of units outside of towns
|
||||
- `market` - see [Market](Map_Objects/Market.md). Trading resources, artifacts, creatures and such
|
||||
- `boat` - see [Boat](Map_Objects/Boat.md). Object to move across different terrains, such as water
|
||||
- `hillFort` - TODO: documentation. See config files in vcmi installation for reference
|
||||
- `shipyard` - TODO: documentation. See config files in vcmi installation for reference
|
||||
- `terrain` - Defines terrain overlays such as magic grounds. TODO: documentation. See config files in vcmi installation for reference
|
||||
|
||||
### Common types
|
||||
These are types that don't have configurable properties, however it is possible to add additional map templates for this objects, for use in editor or in random maps generator
|
||||
|
||||
- `static` - Defines unpassable static map obstacles that can be used by RMG
|
||||
- `generic` - Defines empty object type that provides no functionality. Note that unlike `static`, objects of this type are never used by RMG
|
||||
- `borderGate`
|
||||
- `borderGuard`
|
||||
- `lighthouse`
|
||||
- `magi`
|
||||
- `mine`
|
||||
- `obelisk`
|
||||
- `subterraneanGate`
|
||||
- `whirlpool`
|
||||
- `resource`
|
||||
- `denOfThieves`
|
||||
- `garrison`
|
||||
- `keymaster`
|
||||
- `pandora`
|
||||
- `prison`
|
||||
- `questGuard`
|
||||
- `seerHut`
|
||||
- `sign`
|
||||
- `siren`
|
||||
- `monolith`
|
||||
|
||||
### Internal types
|
||||
These are internal types that are generally not available for modding and are handled by vcmi internally.
|
||||
|
||||
- `hero`
|
||||
- `town`
|
||||
- `monster`
|
||||
- `randomArtifact`
|
||||
- `randomHero`
|
||||
- `randomResource`
|
||||
- `randomTown`
|
||||
- `randomMonster`
|
||||
- `randomDwelling`
|
||||
- `artifact`
|
||||
- `event`
|
||||
- `heroPlaceholder`
|
||||
|
||||
## Object type format
|
||||
|
||||
``` javascript
|
||||
@ -151,4 +199,4 @@ Full object consists from 3 parts:
|
||||
"zIndex": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
@ -3,7 +3,116 @@
|
||||
Reward types for clearing creature bank are limited to resources, creatures, artifacts and spell.
|
||||
Format of rewards is same as in [Rewardable Objects](Rewardable.md)
|
||||
|
||||
``` javascript
|
||||
Deprecated in 1.6. Please use [Rewardable Objects](Rewardable.md) instead. See Conversion from 1.5 format section below for help with migration
|
||||
|
||||
### Example
|
||||
This example defines a rewardable object with functionality similar of H3 creature bank.
|
||||
See [Rewardable Objects](Rewardable.md) for detailed documentation of these properties.
|
||||
```jsonc
|
||||
{
|
||||
"name" : "Cyclops Stockpile",
|
||||
|
||||
// Generic message to ask player whether he wants to attack a creature bank, can be replaced with custom string
|
||||
"onGuardedMessage" : 32,
|
||||
|
||||
// Generic message to inform player that bank was already cleared
|
||||
"onVisitedMessage" : 33,
|
||||
|
||||
// As an alternative to a generic message you can define 'reward'
|
||||
// that will be granted for visiting already cleared bank, such as morale debuff
|
||||
"onVisited" : [
|
||||
{
|
||||
"message" : 123, // "Such a despicable act reduces your army's morale."
|
||||
"bonuses" : [ { "type" : "MORALE", "val" : -1, "duration" : "ONE_BATTLE", "description" : 99 } ]
|
||||
}
|
||||
],
|
||||
"visitMode" : "once", // Banks never reset
|
||||
// Defines layout of guards. To emulate H3 logic,
|
||||
// use 'creatureBankNarrow' if guardian units are narrow (1-tile units)
|
||||
// or, 'creatureBankWide' if defenders are double-hex units
|
||||
"guardsLayout" : "creatureBankNarrow",
|
||||
"rewards" : [
|
||||
{
|
||||
"message" : 34,
|
||||
"appearChance" : { "min" : 0, "max" : 30 },
|
||||
"guards" : [
|
||||
{ "amount" : 4, "type" : "cyclop" },
|
||||
{ "amount" : 4, "type" : "cyclop" },
|
||||
{ "amount" : 4, "type" : "cyclop", "upgradeChance" : 50 },
|
||||
{ "amount" : 4, "type" : "cyclop" },
|
||||
{ "amount" : 4, "type" : "cyclop" }
|
||||
],
|
||||
"resources" : {
|
||||
"gold" : 4000
|
||||
}
|
||||
},
|
||||
{
|
||||
"message" : 34,
|
||||
"appearChance" : { "min" : 30, "max" : 60 },
|
||||
"guards" : [
|
||||
{ "amount" : 6, "type" : "cyclop" },
|
||||
{ "amount" : 6, "type" : "cyclop" },
|
||||
{ "amount" : 6, "type" : "cyclop", "upgradeChance" : 50 },
|
||||
{ "amount" : 6, "type" : "cyclop" },
|
||||
{ "amount" : 6, "type" : "cyclop" }
|
||||
],
|
||||
"resources" : {
|
||||
"gold" : 6000
|
||||
}
|
||||
},
|
||||
{
|
||||
"message" : 34,
|
||||
"appearChance" : { "min" : 60, "max" : 90 },
|
||||
"guards" : [
|
||||
{ "amount" : 8, "type" : "cyclop" },
|
||||
{ "amount" : 8, "type" : "cyclop" },
|
||||
{ "amount" : 8, "type" : "cyclop", "upgradeChance" : 50 },
|
||||
{ "amount" : 8, "type" : "cyclop" },
|
||||
{ "amount" : 8, "type" : "cyclop" }
|
||||
],
|
||||
"resources" : {
|
||||
"gold" : 8000
|
||||
}
|
||||
},
|
||||
{
|
||||
"message" : 34,
|
||||
"appearChance" : { "min" : 90, "max" : 100 },
|
||||
"guards" : [
|
||||
{ "amount" : 10, "type" : "cyclop" },
|
||||
{ "amount" : 10, "type" : "cyclop" },
|
||||
{ "amount" : 10, "type" : "cyclop", "upgradeChance" : 50 },
|
||||
{ "amount" : 10, "type" : "cyclop" },
|
||||
{ "amount" : 10, "type" : "cyclop" }
|
||||
],
|
||||
"resources" : {
|
||||
"gold" : 10000
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
```
|
||||
|
||||
### Conversion from 1.5 format
|
||||
This is a list of changes that needs to be done to bank config to migrate it to 1.6 system. See [Rewardable Objects](Rewardable.md) documentation for description of new fields
|
||||
|
||||
- If your object type has defined `handler`, change its value from `bank` to `configurable`
|
||||
|
||||
- If your object has non-zero `resetDuration`, replace with `resetParameters` entry
|
||||
|
||||
- For each possible level, replace `chance` with `appearChance` entry
|
||||
|
||||
- If you have `combat_value` or `field` entries inside 'reward' - remove them. These fields are unused in both 1.5 and in 1.6
|
||||
|
||||
- Rename `levels` entry to `rewards`
|
||||
|
||||
- Add property `"visitMode" : "once"`
|
||||
- Add property `"onGuardedMessage" : 119`, optionally - replace with custom message for object visit
|
||||
- Add property `"onVisitedMessage" : 33`, optionally - custom message or morale debuff
|
||||
- Add property `"message" : 34`, to every level of your reward, optionally - replace with custom message
|
||||
|
||||
### Old format (1.5 or earlier)
|
||||
|
||||
``` jsonc
|
||||
{
|
||||
/// If true, battle setup will be like normal - Attacking player on the left, enemy on the right
|
||||
"regularUnitPlacement" : true,
|
||||
@ -63,4 +172,4 @@ Format of rewards is same as in [Rewardable Objects](Rewardable.md)
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
```
|
||||
|
@ -117,6 +117,9 @@ Rewardable object is defined similarly to other objects, with key difference bei
|
||||
// Message that will be shown if there are multiple selectable awards to choose from
|
||||
"onSelectMessage" : "",
|
||||
|
||||
// Message that will be shown if object has undefeated guards
|
||||
"onGuardedMessage" : "",
|
||||
|
||||
// Message that will be shown if this object has been already visited before
|
||||
"onVisitedMessage" : "{Warehouse of Crystal}\r\n\r\nThe owner of the storage is apologising: 'I am sorry Milord, no crystal here. Please, return next week!'",
|
||||
|
||||
@ -125,10 +128,22 @@ Rewardable object is defined similarly to other objects, with key difference bei
|
||||
"onVisited" : [
|
||||
]
|
||||
|
||||
// Layout of units in the battle (only used if guards are present)
|
||||
// Predefined values:
|
||||
// "default" - attacker is on the left, defender is on the right, war machine, tactics, and battlefield obstacles are present
|
||||
// "creatureBankNarrow" - emulates H3 logic for banks with narrow (1-tile wide) units
|
||||
// "creatureBankWide" - emulates H3 logic for banks with wide units that take 2 hexes
|
||||
// Additionally, it is possible to define new layouts, see "layouts" field in (vcmi install)/config/gameConfig.json file
|
||||
"guardsLayout" : "default"
|
||||
|
||||
// if true, then player can refuse from reward and don't select anything
|
||||
// Note that in this case object will not become "visited" and can still be revisited later
|
||||
"canRefuse": true,
|
||||
|
||||
// If set to true, then this object can be visited from land when placed next to a coast.
|
||||
// NOTE: make sure that object also has "blockedVisitable" set to true. Othervice, behavior is undefined
|
||||
"coastVisitable" : true
|
||||
|
||||
// Controls when object state will be reset, allowing potential revisits. See Reset Parameters definition section
|
||||
"resetParameters" : {
|
||||
}
|
||||
@ -479,8 +494,6 @@ Keep in mind, that all randomization is performed on map load and on object rese
|
||||
],
|
||||
```
|
||||
|
||||
canLearnSpells
|
||||
|
||||
### Creatures
|
||||
- Can be used as limiter
|
||||
- Can be used as reward, to give new creatures to a hero
|
||||
@ -496,6 +509,21 @@ canLearnSpells
|
||||
],
|
||||
```
|
||||
|
||||
### Guards
|
||||
- When used in a reward, these creatures will be added to guards of the objects
|
||||
- Hero must defeat all guards before being able to receive rewards
|
||||
- Guards are only reset when object rewards are reset
|
||||
- Requires `guardsLayout` property to be set in main part of object configuration
|
||||
- It is possible to add up to 7 slots of creatures
|
||||
- Guards of the same creature type will never merge or rearrange their stacks
|
||||
```jsonc
|
||||
"guards" : [
|
||||
{ "type" : "archer", "amount" : 20 },
|
||||
{ "type" : "archer", "amount" : 20, "upgradeChance" : 30 },
|
||||
{ "type" : "archer", "amount" : 20 }
|
||||
],
|
||||
```
|
||||
|
||||
### Creatures Change
|
||||
- Can NOT be used as limiter
|
||||
- Can be used as reward, to replace creatures in hero army. It is possible to use this parameter both for upgrades of creatures as well as for changing them into completely unrelated creature, e.g. similar to Skeleton Transformer
|
||||
|
@ -56,8 +56,10 @@ Random Map Generator:
|
||||
|
||||
Game Entities:
|
||||
- [Artifact](Entities_Format/Artifact_Format.md)
|
||||
- [Creature](Entities_Format/Creature_Format.md)
|
||||
- [Faction](Entities_Format/Faction_Format.md)
|
||||
- [Creature Requirement](Entities_Format/Creature_Format.md)
|
||||
- [Creature Help](Entities_Format/Creature_Help.md)
|
||||
- [Faction Requirement](Entities_Format/Faction_Format.md)
|
||||
- [Faction Help](Entities_Format/Faction_Help.md)
|
||||
- [Hero Class](Entities_Format/Hero_Class_Format.md)
|
||||
- [Hero Type](Entities_Format/Hero_Type_Format.md)
|
||||
- [Spell](Entities_Format/Spell_Format.md)
|
||||
|
@ -35,10 +35,16 @@ VCMI allows translating game data into languages other than English. In order to
|
||||
- Copy existing translation, such as English translation from here: https://github.com/vcmi-mods/h3-for-vcmi-englisation (delete sound and video folders)
|
||||
- Rename mod to indicate your language, preferred form is "(language)-translation"
|
||||
- Update mod.json to match your mod
|
||||
- Translate all texts strings from game.json, campaigns.json and maps.json
|
||||
- Translate all texts strings from `game.json`, `campaigns.json` and `maps.json`
|
||||
- Replace images in data and sprites with translated ones (or delete it if you don't want to translate them)
|
||||
- If unicode characters needed for language: Create a submod with a free font like here: https://github.com/vcmi-mods/vietnamese-translation/tree/vcmi-1.4/vietnamese-translation/mods/VietnameseTrueTypeFonts
|
||||
|
||||
If you can't produce some content on your own (like the images or the sounds):
|
||||
- Create a `README.md` file at the root of the mod
|
||||
- Write into the file the translations and the <ins>detailled</ins> location
|
||||
|
||||
This way, a contributor that is not a native speaker can do it for you in the future.
|
||||
|
||||
If you have already existing Heroes III translation you can:
|
||||
|
||||
- Install VCMI and select your localized Heroes III data files for VCMI data files
|
||||
@ -75,7 +81,7 @@ After this, you can set language in Launcher to your language and start game. Al
|
||||
VCMI Launcher and Map Editor use translation system provided by Qt framework so it requires slightly different approach than in-game translations:
|
||||
|
||||
- Install Qt Linguist. You can find find standalone version here: https://download.qt.io/linguist_releases/
|
||||
- Open `<VCMI Sources>/launcher/translation/` directory, copy english.ts file and rename it to your language
|
||||
- Open `<VCMI Sources>/launcher/translation/` directory, copy `english.ts` file and rename it to your language
|
||||
- Launch Qt Linguist, select Open and navigate to your copied file
|
||||
- Select any untranslated string, enter translation in field below, and click "Done and Next" (Ctrl+Return) to navigate to next untranslated string
|
||||
- Once translation has been finished, save resulting file.
|
||||
@ -129,7 +135,7 @@ After that, start Launcher, switch to Help tab and open "log files directory". Y
|
||||
If your mod also contains maps or campaigns that you want to translate, then use '/translate maps' command instead.
|
||||
|
||||
### Translating mod information
|
||||
In order to display information in Launcher in language selected by user add following block into your mod.json:
|
||||
In order to display information in Launcher in language selected by user add following block into your `mod.json`:
|
||||
```
|
||||
"<language>" : {
|
||||
"name" : "<translated name>",
|
||||
@ -140,7 +146,7 @@ In order to display information in Launcher in language selected by user add fol
|
||||
]
|
||||
},
|
||||
```
|
||||
However, normally you don't need to use block for English. Instead, English text should remain in root section of your mod.json file, to be used when game can not find translated version.
|
||||
However, normally you don't need to use block for English. Instead, English text should remain in root section of your `mod.json` file, to be used when game can not find translated version.
|
||||
|
||||
### Translating in-game strings
|
||||
|
||||
@ -152,8 +158,8 @@ Use any text editor (Notepad++ is recommended for Windows) and translate all str
|
||||
|
||||
### Adding new languages
|
||||
In order to add new language it needs to be added in multiple locations in source code:
|
||||
- Generate new .ts files for launcher and map editor, either by running `lupdate` with name of new .ts or by copying english.ts and editing language tag in the header.
|
||||
- Add new language into lib/Languages.h entry. This will trigger static_assert's in places that needs an update in code
|
||||
- Generate new .ts files for launcher and map editor, either by running `lupdate` with name of new .ts or by copying `english.ts` and editing language tag in the header.
|
||||
- Add new language into `lib/Languages.h` entry. This will trigger static_assert's in places that needs an update in code
|
||||
- Add new language into json schemas validation list - settings schema and mod schema
|
||||
- Add new language into mod json format - in order to allow translation into new language
|
||||
|
||||
@ -161,14 +167,14 @@ Also, make full search for a name of an existing language to ensure that there a
|
||||
|
||||
### Updating translation of Launcher and Map Editor to include new strings
|
||||
|
||||
At the moment, build system will generate binary translation files (.qs) that can be opened by Qt.
|
||||
At the moment, build system will generate binary translation files (`.qs`) that can be opened by Qt.
|
||||
However, any new or changed lines will not be added into existing .ts files.
|
||||
In order to update .ts files manually, open command line shell in `mapeditor` or `launcher` source directories and execute command
|
||||
In order to update `.ts` files manually, open command line shell in `mapeditor` or `launcher` source directories and execute command
|
||||
```
|
||||
lupdate -no-obsolete * -ts translation/*.ts
|
||||
```
|
||||
|
||||
This will remove any no longer existing lines from translation and add any new lines for all translations. If you want to keep old lines, remove `-no-obsolete` key from the command
|
||||
This will remove any no longer existing lines from translation and add any new lines for all translations. If you want to keep old lines, remove `-no-obsolete` key from the command.
|
||||
There *may* be a way to do the same via QtCreator UI or via CMake, if you find one feel free to update this information.
|
||||
|
||||
### Updating translation of Launcher and Map Editor using new .ts file from translators
|
||||
|
@ -74,7 +74,7 @@
|
||||
<message>
|
||||
<location filename="../aboutProject/aboutproject_moc.ui" line="227"/>
|
||||
<source>Configuration files directory</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Dossier de fichiers de configuration</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../aboutProject/aboutproject_moc.ui" line="290"/>
|
||||
@ -234,7 +234,7 @@
|
||||
<message>
|
||||
<location filename="../modManager/cmodlistview_moc.ui" line="105"/>
|
||||
<source>Reload repositories</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Recharger les dossiers</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../modManager/cmodlistview_moc.ui" line="163"/>
|
||||
@ -452,7 +452,7 @@
|
||||
<message>
|
||||
<location filename="../modManager/cmodlistview_moc.cpp" line="665"/>
|
||||
<source>Replace config file?</source>
|
||||
<translation>Remplacer le fichier de configuration?</translation>
|
||||
<translation>Remplacer le fichier de configuration ?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../modManager/cmodlistview_moc.cpp" line="665"/>
|
||||
@ -462,7 +462,7 @@
|
||||
<message>
|
||||
<location filename="../modManager/cmodlistview_moc.cpp" line="708"/>
|
||||
<source>Downloading %1. %p% (%v MB out of %m MB) finished</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Téléchargement %1. %p% (%v Mo sur %m Mo) terminé</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../modManager/cmodlistview_moc.cpp" line="733"/>
|
||||
@ -656,162 +656,162 @@ Installer les téchargements réussis?</translation>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="490"/>
|
||||
<source>Online Lobby port</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Port de la salle d'attente en ligne</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="331"/>
|
||||
<source>Autocombat AI in battles</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>IA de combat automatique dans les batailles</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="352"/>
|
||||
<source>Sticks Sensitivity</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Sensibilité au batons</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="618"/>
|
||||
<source>Automatic (Linear)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Automatique (Linéaire)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="798"/>
|
||||
<source>Haptic Feedback</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Retour Tactile</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="835"/>
|
||||
<source>Software Cursor</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Curseur Logiciel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="1166"/>
|
||||
<source>Automatic</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Automatique</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="1171"/>
|
||||
<source>None</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Aucun</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="1176"/>
|
||||
<source>xBRZ x2</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>xBRZ x2</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="1181"/>
|
||||
<source>xBRZ x3</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>xBRZ x3</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="1186"/>
|
||||
<source>xBRZ x4</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>xBRZ x4</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="138"/>
|
||||
<source>Online Lobby address</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Adresse de la salle d'attente en ligne</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="1158"/>
|
||||
<source>Upscaling Filter</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Filtre d'Agrandissement</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="317"/>
|
||||
<source>Use Relative Pointer Mode</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Utiliser le Mode de Pointeur Relatif</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="608"/>
|
||||
<source>Nearest</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Le plus Proche</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="613"/>
|
||||
<source>Linear</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Linéaire</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="750"/>
|
||||
<source>Input - Touchscreen</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Entrée - Écran tactile</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="854"/>
|
||||
<source>Network</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Réseau</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="893"/>
|
||||
<source>Downscaling Filter</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Filtre de Rétrécissement</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="1144"/>
|
||||
<source>Show Tutorial again</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Remontrer le Didacticiel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="1151"/>
|
||||
<source>Reset</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Réinitialiser</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="536"/>
|
||||
<source>Audio</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Audio</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="842"/>
|
||||
<source>Relative Pointer Speed</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Vitesse de Pointeur Relatif</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="1137"/>
|
||||
<source>Music Volume</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Volume de la Musique</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="767"/>
|
||||
<source>Ignore SSL errors</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Ignorer les erreurs SSL</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="943"/>
|
||||
<source>Input - Mouse</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Entrée - Sourie</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="345"/>
|
||||
<source>Long Touch Duration</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Durée de Touche Prolongée</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="115"/>
|
||||
<source>%</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>%</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="1037"/>
|
||||
<source>Controller Click Tolerance</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Tolérance au Clic de Contrôleur</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="359"/>
|
||||
<source>Touch Tap Tolerance</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Tolérance à la Frappe de Touche</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="1020"/>
|
||||
<source>Input - Controller</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Entrée - Contrôleur</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="1086"/>
|
||||
<source>Sound Volume</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Volume du Son</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="389"/>
|
||||
@ -913,12 +913,12 @@ Mode exclusif plein écran - le jeu couvrira l"intégralité de votre écra
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="828"/>
|
||||
<source>Mouse Click Tolerance</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Tolérance au Clic de Sourie</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="94"/>
|
||||
<source>Sticks Acceleration</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Accelération de Bâton</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="101"/>
|
||||
@ -1059,12 +1059,12 @@ Mode exclusif plein écran - le jeu couvrira l"intégralité de votre écra
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="297"/>
|
||||
<source>Use offline installer from gog.com</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Utiliser l'installeur hors ligne depuis gog.com</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="310"/>
|
||||
<source>You can manually copy directories Maps, Data and Mp3 from the original game directory to VCMI data directory that you can see on top of this page</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Vous pouvez copier manuellement les dossiers de Maps, Data et Mp3 depuis le dossier de jeu d'origine vers le dossier data VCMI que vous pouvez voir en haut de cette page</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="329"/>
|
||||
@ -1074,22 +1074,22 @@ Mode exclusif plein écran - le jeu couvrira l"intégralité de votre écra
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="347"/>
|
||||
<source>Manual Installation</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Installation Manuelle</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="388"/>
|
||||
<source>Installing... %p%</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Installation... %p%</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="417"/>
|
||||
<source>If you already have Heroes III files on your device, you can select this directory and VCMI will copy the existing data automatically.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Si vous avez déjà les fichiers Heroes III sur votre appareil, vous pouvez sélectionner ce dossier et VCMI copiera automatiquement les données existantes.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="459"/>
|
||||
<source>Copy existing files</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Copier les fichiers existants</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="488"/>
|
||||
@ -1100,7 +1100,8 @@ Mode exclusif plein écran - le jeu couvrira l"intégralité de votre écra
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="504"/>
|
||||
<source>If you own Heroes III on gog.com you can download backup offline installer from gog.com, and VCMI will import Heroes III data using offline installer.
|
||||
Offline installer consists of two parts, .exe and .bin. Make sure you download both of them.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Si vous possédez Heroes III sur gog.com, vous pouvez télécharger le programme d'installation hors ligne de sauvegarde depuis gog.com, et VCMI importera les données de Heroes III à l'aide du programme d'installation hors ligne.
|
||||
Le programme d'installation hors ligne se compose de deux parties, .exe et .bin. Assurez-vous de les télécharger tous les deux.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="622"/>
|
||||
@ -1246,7 +1247,7 @@ Heroes® of Might and Magic® III HD n"est actuellement pas pris en charge
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.cpp" line="330"/>
|
||||
<source>File cannot opened</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Le fichier ne peut pas être ouvert</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.cpp" line="336"/>
|
||||
@ -1291,23 +1292,24 @@ Veuillez selectionner un dossier ou les données de Heroes III sont présentes.<
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.cpp" line="387"/>
|
||||
<source>You've provided GOG Galaxy installer! This file doesn't contain the game. Please download the offline backup game installer!</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Vous avez fourni le programme d'installation de GOG Galaxy ! Ce fichier ne contient pas le jeu. Veuillez télécharger le programme d'installation de sauvegarde hors ligne du jeu !</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.cpp" line="412"/>
|
||||
<source>Stream error while extracting files!
|
||||
error reason: </source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Erreur de flux lors de l'extraction des fichiers !
|
||||
Raison de l'erreur : </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.cpp" line="425"/>
|
||||
<source>Not a supported Inno Setup installer!</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Programme d’installation Inno Setup non pris en charge !</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.cpp" line="436"/>
|
||||
<source>Extracting error!</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Erreur d'extraction !</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.cpp" line="506"/>
|
||||
@ -1476,13 +1478,14 @@ Veuillez sélectionner un dossier contenant les données de Heroes III: Complete
|
||||
<message>
|
||||
<location filename="../main.cpp" line="122"/>
|
||||
<source>Error starting executable</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Erreur lors du démarrage de l'exécutable</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../main.cpp" line="123"/>
|
||||
<source>Failed to start %1
|
||||
Reason: %2</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Échec de démarrage %1
|
||||
Raison : %2</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
1514
launcher/translation/swedish.ts
Normal file
1514
launcher/translation/swedish.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -48,6 +48,7 @@ set(lib_MAIN_SRCS
|
||||
battle/BattleAttackInfo.cpp
|
||||
battle/BattleHex.cpp
|
||||
battle/BattleInfo.cpp
|
||||
battle/BattleLayout.cpp
|
||||
battle/BattleProxy.cpp
|
||||
battle/BattleStateInfoForRetreat.cpp
|
||||
battle/CBattleInfoCallback.cpp
|
||||
@ -404,6 +405,7 @@ set(lib_MAIN_HEADERS
|
||||
battle/BattleAttackInfo.h
|
||||
battle/BattleHex.h
|
||||
battle/BattleInfo.h
|
||||
battle/BattleLayout.h
|
||||
battle/BattleSide.h
|
||||
battle/BattleStateInfoForRetreat.h
|
||||
battle/BattleProxy.h
|
||||
|
@ -153,6 +153,8 @@ public:
|
||||
//TODO: boost::array, bool if possible
|
||||
boost::multi_array<ui8, 3> fogOfWarMap; //[z][x][y] true - visible, false - hidden
|
||||
|
||||
std::set<ObjectInstanceID> scoutedObjects;
|
||||
|
||||
TeamState();
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
@ -173,6 +175,9 @@ public:
|
||||
|
||||
h & fogOfWarMap;
|
||||
h & static_cast<CBonusSystemNode&>(*this);
|
||||
|
||||
if (h.version >= Handler::Version::REWARDABLE_BANKS)
|
||||
h & scoutedObjects;
|
||||
}
|
||||
|
||||
};
|
||||
|
@ -37,6 +37,7 @@ GameSettings::GameSettings() = default;
|
||||
GameSettings::~GameSettings() = default;
|
||||
|
||||
const std::vector<GameSettings::SettingOption> GameSettings::settingProperties = {
|
||||
{EGameSettings::BANKS_SHOW_GUARDS_COMPOSITION, "banks", "showGuardsComposition" },
|
||||
{EGameSettings::BONUSES_GLOBAL, "bonuses", "global" },
|
||||
{EGameSettings::BONUSES_PER_HERO, "bonuses", "perHero" },
|
||||
{EGameSettings::COMBAT_ATTACK_POINT_DAMAGE_FACTOR, "combat", "attackPointDamageFactor" },
|
||||
@ -47,34 +48,46 @@ const std::vector<GameSettings::SettingOption> GameSettings::settingProperties =
|
||||
{EGameSettings::COMBAT_DEFENSE_POINT_DAMAGE_FACTOR_CAP, "combat", "defensePointDamageFactorCap" },
|
||||
{EGameSettings::COMBAT_GOOD_LUCK_DICE, "combat", "goodLuckDice" },
|
||||
{EGameSettings::COMBAT_GOOD_MORALE_DICE, "combat", "goodMoraleDice" },
|
||||
{EGameSettings::COMBAT_LAYOUTS, "combat", "layouts" },
|
||||
{EGameSettings::COMBAT_ONE_HEX_TRIGGERS_OBSTACLES, "combat", "oneHexTriggersObstacles" },
|
||||
{EGameSettings::CREATURES_ALLOW_ALL_FOR_DOUBLE_MONTH, "creatures", "allowAllForDoubleMonth" },
|
||||
{EGameSettings::CREATURES_ALLOW_RANDOM_SPECIAL_WEEKS, "creatures", "allowRandomSpecialWeeks" },
|
||||
{EGameSettings::CREATURES_DAILY_STACK_EXPERIENCE, "creatures", "dailyStackExperience" },
|
||||
{EGameSettings::CREATURES_WEEKLY_GROWTH_CAP, "creatures", "weeklyGrowthCap" },
|
||||
{EGameSettings::CREATURES_WEEKLY_GROWTH_PERCENT, "creatures", "weeklyGrowthPercent" },
|
||||
{EGameSettings::DIMENSION_DOOR_EXPOSES_TERRAIN_TYPE, "spells", "dimensionDoorExposesTerrainType" },
|
||||
{EGameSettings::DIMENSION_DOOR_FAILURE_SPENDS_POINTS, "spells", "dimensionDoorFailureSpendsPoints" },
|
||||
{EGameSettings::DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES, "spells", "dimensionDoorOnlyToUncoveredTiles"},
|
||||
{EGameSettings::DIMENSION_DOOR_TOURNAMENT_RULES_LIMIT, "spells", "dimensionDoorTournamentRulesLimit"},
|
||||
{EGameSettings::DIMENSION_DOOR_TRIGGERS_GUARDS, "spells", "dimensionDoorTriggersGuards" },
|
||||
{EGameSettings::DWELLINGS_ACCUMULATE_WHEN_NEUTRAL, "dwellings", "accumulateWhenNeutral" },
|
||||
{EGameSettings::DWELLINGS_ACCUMULATE_WHEN_OWNED, "dwellings", "accumulateWhenOwned" },
|
||||
{EGameSettings::DWELLINGS_MERGE_ON_RECRUIT, "dwellings", "mergeOnRecruit" },
|
||||
{EGameSettings::HEROES_BACKPACK_CAP, "heroes", "backpackSize" },
|
||||
{EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS, "heroes", "minimalPrimarySkills" },
|
||||
{EGameSettings::HEROES_PER_PLAYER_ON_MAP_CAP, "heroes", "perPlayerOnMapCap" },
|
||||
{EGameSettings::HEROES_PER_PLAYER_TOTAL_CAP, "heroes", "perPlayerTotalCap" },
|
||||
{EGameSettings::HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS, "heroes", "retreatOnWinWithoutTroops" },
|
||||
{EGameSettings::HEROES_STARTING_STACKS_CHANCES, "heroes", "startingStackChances" },
|
||||
{EGameSettings::HEROES_BACKPACK_CAP, "heroes", "backpackSize" },
|
||||
{EGameSettings::HEROES_TAVERN_INVITE, "heroes", "tavernInvite" },
|
||||
{EGameSettings::HEROES_MINIMAL_PRIMARY_SKILLS, "heroes", "minimalPrimarySkills" },
|
||||
{EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA, "mapFormat", "restorationOfErathia" },
|
||||
{EGameSettings::MAP_FORMAT_ARMAGEDDONS_BLADE, "mapFormat", "armageddonsBlade" },
|
||||
{EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH, "mapFormat", "shadowOfDeath" },
|
||||
{EGameSettings::MAP_FORMAT_CHRONICLES, "mapFormat", "chronicles" },
|
||||
{EGameSettings::MAP_FORMAT_HORN_OF_THE_ABYSS, "mapFormat", "hornOfTheAbyss" },
|
||||
{EGameSettings::MAP_FORMAT_IN_THE_WAKE_OF_GODS, "mapFormat", "inTheWakeOfGods" },
|
||||
{EGameSettings::MAP_FORMAT_JSON_VCMI, "mapFormat", "jsonVCMI" },
|
||||
{EGameSettings::MAP_FORMAT_RESTORATION_OF_ERATHIA, "mapFormat", "restorationOfErathia" },
|
||||
{EGameSettings::MAP_FORMAT_SHADOW_OF_DEATH, "mapFormat", "shadowOfDeath" },
|
||||
{EGameSettings::MARKETS_BLACK_MARKET_RESTOCK_PERIOD, "markets", "blackMarketRestockPeriod" },
|
||||
{EGameSettings::BANKS_SHOW_GUARDS_COMPOSITION, "banks", "showGuardsComposition" },
|
||||
{EGameSettings::MODULE_COMMANDERS, "modules", "commanders" },
|
||||
{EGameSettings::MODULE_STACK_ARTIFACT, "modules", "stackArtifact" },
|
||||
{EGameSettings::MODULE_STACK_EXPERIENCE, "modules", "stackExperience" },
|
||||
{EGameSettings::PATHFINDER_IGNORE_GUARDS, "pathfinder", "ignoreGuards" },
|
||||
{EGameSettings::PATHFINDER_ORIGINAL_FLY_RULES, "pathfinder", "originalFlyRules" },
|
||||
{EGameSettings::PATHFINDER_USE_BOAT, "pathfinder", "useBoat" },
|
||||
{EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_RANDOM, "pathfinder", "useMonolithOneWayRandom" },
|
||||
{EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE, "pathfinder", "useMonolithOneWayUnique" },
|
||||
{EGameSettings::PATHFINDER_USE_MONOLITH_TWO_WAY, "pathfinder", "useMonolithTwoWay" },
|
||||
{EGameSettings::PATHFINDER_USE_WHIRLPOOL, "pathfinder", "useWhirlpool" },
|
||||
{EGameSettings::TEXTS_ARTIFACT, "textData", "artifact" },
|
||||
{EGameSettings::TEXTS_CREATURE, "textData", "creature" },
|
||||
{EGameSettings::TEXTS_FACTION, "textData", "faction" },
|
||||
@ -85,18 +98,6 @@ const std::vector<GameSettings::SettingOption> GameSettings::settingProperties =
|
||||
{EGameSettings::TEXTS_ROAD, "textData", "road" },
|
||||
{EGameSettings::TEXTS_SPELL, "textData", "spell" },
|
||||
{EGameSettings::TEXTS_TERRAIN, "textData", "terrain" },
|
||||
{EGameSettings::PATHFINDER_IGNORE_GUARDS, "pathfinder", "ignoreGuards" },
|
||||
{EGameSettings::PATHFINDER_USE_BOAT, "pathfinder", "useBoat" },
|
||||
{EGameSettings::PATHFINDER_USE_MONOLITH_TWO_WAY, "pathfinder", "useMonolithTwoWay" },
|
||||
{EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE, "pathfinder", "useMonolithOneWayUnique" },
|
||||
{EGameSettings::PATHFINDER_USE_MONOLITH_ONE_WAY_RANDOM, "pathfinder", "useMonolithOneWayRandom" },
|
||||
{EGameSettings::PATHFINDER_USE_WHIRLPOOL, "pathfinder", "useWhirlpool" },
|
||||
{EGameSettings::PATHFINDER_ORIGINAL_FLY_RULES, "pathfinder", "originalFlyRules" },
|
||||
{EGameSettings::DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES, "spells", "dimensionDoorOnlyToUncoveredTiles"},
|
||||
{EGameSettings::DIMENSION_DOOR_EXPOSES_TERRAIN_TYPE, "spells", "dimensionDoorExposesTerrainType" },
|
||||
{EGameSettings::DIMENSION_DOOR_FAILURE_SPENDS_POINTS, "spells", "dimensionDoorFailureSpendsPoints" },
|
||||
{EGameSettings::DIMENSION_DOOR_TRIGGERS_GUARDS, "spells", "dimensionDoorTriggersGuards" },
|
||||
{EGameSettings::DIMENSION_DOOR_TOURNAMENT_RULES_LIMIT, "spells", "dimensionDoorTournamentRulesLimit"},
|
||||
{EGameSettings::TOWNS_BUILDINGS_PER_TURN_CAP, "towns", "buildingsPerTurnCap" },
|
||||
{EGameSettings::TOWNS_STARTING_DWELLING_CHANCES, "towns", "startingDwellingChances" },
|
||||
};
|
||||
|
@ -28,6 +28,7 @@ struct TeleportDialog;
|
||||
struct StackLocation;
|
||||
struct ArtifactLocation;
|
||||
struct BankConfig;
|
||||
struct BattleLayout;
|
||||
class CCreatureSet;
|
||||
class CStackBasicDescriptor;
|
||||
class CGCreature;
|
||||
@ -128,9 +129,8 @@ public:
|
||||
virtual void heroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero)=0;
|
||||
virtual void visitCastleObjects(const CGTownInstance * obj, const CGHeroInstance * hero)=0;
|
||||
virtual void stopHeroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero)=0;
|
||||
virtual void startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank = false, const CGTownInstance *town = nullptr)=0; //use hero=nullptr for no hero
|
||||
virtual void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank = false)=0; //if any of armies is hero, hero will be used
|
||||
virtual void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false)=0; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle
|
||||
virtual void startBattle(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const BattleLayout & layout, const CGTownInstance *town)=0; //use hero=nullptr for no hero
|
||||
virtual void startBattle(const CArmedInstance *army1, const CArmedInstance *army2)=0; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle
|
||||
virtual bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveMove, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL)=0;
|
||||
virtual bool swapGarrisonOnSiege(ObjectInstanceID tid)=0;
|
||||
virtual void giveHeroBonus(GiveBonus * bonus)=0;
|
||||
|
@ -15,6 +15,7 @@ class JsonNode;
|
||||
|
||||
enum class EGameSettings
|
||||
{
|
||||
BANKS_SHOW_GUARDS_COMPOSITION,
|
||||
BONUSES_GLOBAL,
|
||||
BONUSES_PER_HERO,
|
||||
COMBAT_ATTACK_POINT_DAMAGE_FACTOR,
|
||||
@ -25,26 +26,46 @@ enum class EGameSettings
|
||||
COMBAT_DEFENSE_POINT_DAMAGE_FACTOR_CAP,
|
||||
COMBAT_GOOD_LUCK_DICE,
|
||||
COMBAT_GOOD_MORALE_DICE,
|
||||
COMBAT_LAYOUTS,
|
||||
COMBAT_ONE_HEX_TRIGGERS_OBSTACLES,
|
||||
CREATURES_ALLOW_ALL_FOR_DOUBLE_MONTH,
|
||||
CREATURES_ALLOW_RANDOM_SPECIAL_WEEKS,
|
||||
CREATURES_DAILY_STACK_EXPERIENCE,
|
||||
CREATURES_WEEKLY_GROWTH_CAP,
|
||||
CREATURES_WEEKLY_GROWTH_PERCENT,
|
||||
DIMENSION_DOOR_EXPOSES_TERRAIN_TYPE,
|
||||
DIMENSION_DOOR_FAILURE_SPENDS_POINTS,
|
||||
DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES,
|
||||
DIMENSION_DOOR_TOURNAMENT_RULES_LIMIT,
|
||||
DIMENSION_DOOR_TRIGGERS_GUARDS,
|
||||
DWELLINGS_ACCUMULATE_WHEN_NEUTRAL,
|
||||
DWELLINGS_ACCUMULATE_WHEN_OWNED,
|
||||
DWELLINGS_MERGE_ON_RECRUIT,
|
||||
HEROES_BACKPACK_CAP,
|
||||
HEROES_MINIMAL_PRIMARY_SKILLS,
|
||||
HEROES_PER_PLAYER_ON_MAP_CAP,
|
||||
HEROES_PER_PLAYER_TOTAL_CAP,
|
||||
HEROES_RETREAT_ON_WIN_WITHOUT_TROOPS,
|
||||
HEROES_STARTING_STACKS_CHANCES,
|
||||
HEROES_BACKPACK_CAP,
|
||||
HEROES_TAVERN_INVITE,
|
||||
HEROES_MINIMAL_PRIMARY_SKILLS,
|
||||
MAP_FORMAT_ARMAGEDDONS_BLADE,
|
||||
MAP_FORMAT_CHRONICLES,
|
||||
MAP_FORMAT_HORN_OF_THE_ABYSS,
|
||||
MAP_FORMAT_IN_THE_WAKE_OF_GODS,
|
||||
MAP_FORMAT_JSON_VCMI,
|
||||
MAP_FORMAT_RESTORATION_OF_ERATHIA,
|
||||
MAP_FORMAT_SHADOW_OF_DEATH,
|
||||
MARKETS_BLACK_MARKET_RESTOCK_PERIOD,
|
||||
BANKS_SHOW_GUARDS_COMPOSITION,
|
||||
MODULE_COMMANDERS,
|
||||
MODULE_STACK_ARTIFACT,
|
||||
MODULE_STACK_EXPERIENCE,
|
||||
PATHFINDER_IGNORE_GUARDS,
|
||||
PATHFINDER_ORIGINAL_FLY_RULES,
|
||||
PATHFINDER_USE_BOAT,
|
||||
PATHFINDER_USE_MONOLITH_ONE_WAY_RANDOM,
|
||||
PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE,
|
||||
PATHFINDER_USE_MONOLITH_TWO_WAY,
|
||||
PATHFINDER_USE_WHIRLPOOL,
|
||||
TEXTS_ARTIFACT,
|
||||
TEXTS_CREATURE,
|
||||
TEXTS_FACTION,
|
||||
@ -55,28 +76,8 @@ enum class EGameSettings
|
||||
TEXTS_ROAD,
|
||||
TEXTS_SPELL,
|
||||
TEXTS_TERRAIN,
|
||||
MAP_FORMAT_RESTORATION_OF_ERATHIA,
|
||||
MAP_FORMAT_ARMAGEDDONS_BLADE,
|
||||
MAP_FORMAT_SHADOW_OF_DEATH,
|
||||
MAP_FORMAT_CHRONICLES,
|
||||
MAP_FORMAT_HORN_OF_THE_ABYSS,
|
||||
MAP_FORMAT_JSON_VCMI,
|
||||
MAP_FORMAT_IN_THE_WAKE_OF_GODS,
|
||||
PATHFINDER_USE_BOAT,
|
||||
PATHFINDER_IGNORE_GUARDS,
|
||||
PATHFINDER_USE_MONOLITH_TWO_WAY,
|
||||
PATHFINDER_USE_MONOLITH_ONE_WAY_UNIQUE,
|
||||
PATHFINDER_USE_MONOLITH_ONE_WAY_RANDOM,
|
||||
PATHFINDER_USE_WHIRLPOOL,
|
||||
PATHFINDER_ORIGINAL_FLY_RULES,
|
||||
TOWNS_BUILDINGS_PER_TURN_CAP,
|
||||
TOWNS_STARTING_DWELLING_CHANCES,
|
||||
COMBAT_ONE_HEX_TRIGGERS_OBSTACLES,
|
||||
DIMENSION_DOOR_ONLY_TO_UNCOVERED_TILES,
|
||||
DIMENSION_DOOR_EXPOSES_TERRAIN_TYPE,
|
||||
DIMENSION_DOOR_FAILURE_SPENDS_POINTS,
|
||||
DIMENSION_DOOR_TRIGGERS_GUARDS,
|
||||
DIMENSION_DOOR_TOURNAMENT_RULES_LIMIT,
|
||||
|
||||
OPTIONS_COUNT,
|
||||
OPTIONS_BEGIN = BONUSES_GLOBAL
|
||||
|
@ -9,6 +9,8 @@
|
||||
*/
|
||||
#include "StdInc.h"
|
||||
#include "BattleInfo.h"
|
||||
|
||||
#include "BattleLayout.h"
|
||||
#include "CObstacleInstance.h"
|
||||
#include "bonuses/Limiters.h"
|
||||
#include "bonuses/Updaters.h"
|
||||
@ -74,22 +76,6 @@ void BattleInfo::localInit()
|
||||
exportBonuses();
|
||||
}
|
||||
|
||||
namespace CGH
|
||||
{
|
||||
static void readBattlePositions(const JsonNode &node, std::vector< std::vector<int> > & dest)
|
||||
{
|
||||
for(const JsonNode &level : node.Vector())
|
||||
{
|
||||
std::vector<int> pom;
|
||||
for(const JsonNode &value : level.Vector())
|
||||
{
|
||||
pom.push_back(static_cast<int>(value.Float()));
|
||||
}
|
||||
|
||||
dest.push_back(pom);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//RNG that works like H3 one
|
||||
struct RandGen
|
||||
@ -173,22 +159,20 @@ struct RangeGenerator
|
||||
std::function<int()> myRand;
|
||||
};
|
||||
|
||||
BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const BattleField & battlefieldType, BattleSideArray<const CArmedInstance *> armies, BattleSideArray<const CGHeroInstance *> heroes, bool creatureBank, const CGTownInstance * town)
|
||||
BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const BattleField & battlefieldType, BattleSideArray<const CArmedInstance *> armies, BattleSideArray<const CGHeroInstance *> heroes, const BattleLayout & layout, const CGTownInstance * town)
|
||||
{
|
||||
CMP_stack cmpst;
|
||||
auto * curB = new BattleInfo();
|
||||
auto * curB = new BattleInfo(layout);
|
||||
|
||||
for(auto i : { BattleSide::LEFT_SIDE, BattleSide::RIGHT_SIDE})
|
||||
curB->sides[i].init(heroes[i], armies[i]);
|
||||
|
||||
|
||||
std::vector<CStack*> & stacks = (curB->stacks);
|
||||
|
||||
curB->tile = tile;
|
||||
curB->battlefieldType = battlefieldType;
|
||||
curB->round = -2;
|
||||
curB->activeStack = -1;
|
||||
curB->creatureBank = creatureBank;
|
||||
curB->replayAllowed = false;
|
||||
|
||||
if(town)
|
||||
@ -225,7 +209,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
|
||||
}
|
||||
|
||||
//randomize obstacles
|
||||
if (town == nullptr && !creatureBank) //do it only when it's not siege and not creature bank
|
||||
if (layout.obstaclesAllowed)
|
||||
{
|
||||
RandGen r{};
|
||||
auto ourRand = [&](){ return r.rand(); };
|
||||
@ -321,63 +305,40 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
|
||||
}
|
||||
}
|
||||
|
||||
//reading battleStartpos - add creatures AFTER random obstacles are generated
|
||||
//TODO: parse once to some structure
|
||||
BattleSideArray<std::vector<std::vector<int>>> looseFormations;
|
||||
BattleSideArray<std::vector<std::vector<int>>> tightFormations;
|
||||
BattleSideArray<std::vector<std::vector<int>>> creBankFormations;
|
||||
BattleSideArray<int> commanderField;
|
||||
BattleSideArray<int> commanderBank;
|
||||
const JsonNode config(JsonPath::builtin("config/battleStartpos.json"));
|
||||
const JsonVector &positions = config["battle_positions"].Vector();
|
||||
|
||||
CGH::readBattlePositions(positions[0]["levels"], looseFormations[BattleSide::ATTACKER]);
|
||||
CGH::readBattlePositions(positions[1]["levels"], looseFormations[BattleSide::DEFENDER]);
|
||||
CGH::readBattlePositions(positions[2]["levels"], tightFormations[BattleSide::ATTACKER]);
|
||||
CGH::readBattlePositions(positions[3]["levels"], tightFormations[BattleSide::DEFENDER]);
|
||||
CGH::readBattlePositions(positions[4]["levels"], creBankFormations[BattleSide::ATTACKER]);
|
||||
CGH::readBattlePositions(positions[5]["levels"], creBankFormations[BattleSide::DEFENDER]);
|
||||
|
||||
commanderField[BattleSide::ATTACKER] = config["commanderPositions"]["field"][0].Integer();
|
||||
commanderField[BattleSide::DEFENDER] = config["commanderPositions"]["field"][1].Integer();
|
||||
|
||||
commanderBank[BattleSide::ATTACKER] = config["commanderPositions"]["creBank"][0].Integer();
|
||||
commanderBank[BattleSide::DEFENDER] = config["commanderPositions"]["creBank"][1].Integer();
|
||||
|
||||
//adding war machines
|
||||
if(!creatureBank)
|
||||
//Checks if hero has artifact and create appropriate stack
|
||||
auto handleWarMachine = [&](BattleSide side, const ArtifactPosition & artslot, BattleHex hex)
|
||||
{
|
||||
//Checks if hero has artifact and create appropriate stack
|
||||
auto handleWarMachine = [&](BattleSide side, const ArtifactPosition & artslot, BattleHex hex)
|
||||
const CArtifactInstance * warMachineArt = heroes[side]->getArt(artslot);
|
||||
|
||||
if(nullptr != warMachineArt && hex.isValid())
|
||||
{
|
||||
const CArtifactInstance * warMachineArt = heroes[side]->getArt(artslot);
|
||||
CreatureID cre = warMachineArt->artType->getWarMachine();
|
||||
|
||||
if(nullptr != warMachineArt)
|
||||
{
|
||||
CreatureID cre = warMachineArt->artType->getWarMachine();
|
||||
|
||||
if(cre != CreatureID::NONE)
|
||||
curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(cre, 1), side, SlotID::WAR_MACHINES_SLOT, hex);
|
||||
}
|
||||
};
|
||||
|
||||
if(heroes[BattleSide::ATTACKER])
|
||||
{
|
||||
|
||||
handleWarMachine(BattleSide::ATTACKER, ArtifactPosition::MACH1, 52);
|
||||
handleWarMachine(BattleSide::ATTACKER, ArtifactPosition::MACH2, 18);
|
||||
handleWarMachine(BattleSide::ATTACKER, ArtifactPosition::MACH3, 154);
|
||||
if(town && town->fortificationsLevel().wallsHealth > 0)
|
||||
handleWarMachine(BattleSide::ATTACKER, ArtifactPosition::MACH4, 120);
|
||||
if(cre != CreatureID::NONE)
|
||||
curB->generateNewStack(curB->nextUnitId(), CStackBasicDescriptor(cre, 1), side, SlotID::WAR_MACHINES_SLOT, hex);
|
||||
}
|
||||
};
|
||||
|
||||
if(heroes[BattleSide::DEFENDER])
|
||||
{
|
||||
if(!town) //defending hero shouldn't receive ballista (bug #551)
|
||||
handleWarMachine(BattleSide::DEFENDER, ArtifactPosition::MACH1, 66);
|
||||
handleWarMachine(BattleSide::DEFENDER, ArtifactPosition::MACH2, 32);
|
||||
handleWarMachine(BattleSide::DEFENDER, ArtifactPosition::MACH3, 168);
|
||||
}
|
||||
if(heroes[BattleSide::ATTACKER])
|
||||
{
|
||||
auto warMachineHexes = layout.warMachines.at(BattleSide::ATTACKER);
|
||||
|
||||
handleWarMachine(BattleSide::ATTACKER, ArtifactPosition::MACH1, warMachineHexes.at(0));
|
||||
handleWarMachine(BattleSide::ATTACKER, ArtifactPosition::MACH2, warMachineHexes.at(1));
|
||||
handleWarMachine(BattleSide::ATTACKER, ArtifactPosition::MACH3, warMachineHexes.at(2));
|
||||
if(town && town->fortificationsLevel().wallsHealth > 0)
|
||||
handleWarMachine(BattleSide::ATTACKER, ArtifactPosition::MACH4, warMachineHexes.at(3));
|
||||
}
|
||||
|
||||
if(heroes[BattleSide::DEFENDER])
|
||||
{
|
||||
auto warMachineHexes = layout.warMachines.at(BattleSide::DEFENDER);
|
||||
|
||||
if(!town) //defending hero shouldn't receive ballista (bug #551)
|
||||
handleWarMachine(BattleSide::DEFENDER, ArtifactPosition::MACH1, warMachineHexes.at(0));
|
||||
handleWarMachine(BattleSide::DEFENDER, ArtifactPosition::MACH2, warMachineHexes.at(1));
|
||||
handleWarMachine(BattleSide::DEFENDER, ArtifactPosition::MACH3, warMachineHexes.at(2));
|
||||
}
|
||||
//war machines added
|
||||
|
||||
@ -390,20 +351,10 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
|
||||
int k = 0; //stack serial
|
||||
for(auto i = armies[side]->Slots().begin(); i != armies[side]->Slots().end(); i++, k++)
|
||||
{
|
||||
std::vector<int> *formationVector = nullptr;
|
||||
if(armies[side]->formation == EArmyFormation::TIGHT )
|
||||
formationVector = &tightFormations[side][formationNo];
|
||||
else
|
||||
formationVector = &looseFormations[side][formationNo];
|
||||
const BattleHex & pos = layout.units.at(side).at(k);
|
||||
|
||||
if(creatureBank)
|
||||
formationVector = &creBankFormations[side][formationNo];
|
||||
|
||||
BattleHex pos = (k < formationVector->size() ? formationVector->at(k) : 0);
|
||||
if(creatureBank && i->second->type->isDoubleWide())
|
||||
pos += side == BattleSide::RIGHT_SIDE ? BattleHex::LEFT : BattleHex::RIGHT;
|
||||
|
||||
curB->generateNewStack(curB->nextUnitId(), *i->second, side, i->first, pos);
|
||||
if (pos.isValid())
|
||||
curB->generateNewStack(curB->nextUnitId(), *i->second, side, i->first, pos);
|
||||
}
|
||||
}
|
||||
|
||||
@ -412,9 +363,8 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
|
||||
{
|
||||
if (heroes[i] && heroes[i]->commander && heroes[i]->commander->alive)
|
||||
{
|
||||
curB->generateNewStack(curB->nextUnitId(), *heroes[i]->commander, i, SlotID::COMMANDER_SLOT_PLACEHOLDER, creatureBank ? commanderBank[i] : commanderField[i]);
|
||||
curB->generateNewStack(curB->nextUnitId(), *heroes[i]->commander, i, SlotID::COMMANDER_SLOT_PLACEHOLDER, layout.commanders.at(i));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (curB->town)
|
||||
@ -453,8 +403,6 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//tactics
|
||||
bool isTacticsAllowed = !creatureBank; //no tactics in creature banks
|
||||
|
||||
BattleSideArray<int> battleRepositionHex = {};
|
||||
BattleSideArray<int> battleRepositionHexBlock = {};
|
||||
for(auto i : {BattleSide::ATTACKER, BattleSide::DEFENDER})
|
||||
@ -475,7 +423,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const
|
||||
double tactics will be implemented.
|
||||
*/
|
||||
|
||||
if(isTacticsAllowed)
|
||||
if(layout.tacticsAllowed)
|
||||
{
|
||||
if(tacticsSkillDiffAttacker > 0 && tacticsSkillDiffDefender > 0)
|
||||
logGlobal->warn("Double tactics is not implemented, only attacker will have tactics!");
|
||||
@ -523,7 +471,14 @@ CStack * BattleInfo::getStack(int stackID, bool onlyAlive)
|
||||
return const_cast<CStack *>(battleGetStackByID(stackID, onlyAlive));
|
||||
}
|
||||
|
||||
BattleInfo::BattleInfo(const BattleLayout & layout):
|
||||
BattleInfo()
|
||||
{
|
||||
*this->layout = layout;
|
||||
}
|
||||
|
||||
BattleInfo::BattleInfo():
|
||||
layout(std::make_unique<BattleLayout>()),
|
||||
round(-1),
|
||||
activeStack(-1),
|
||||
town(nullptr),
|
||||
@ -535,6 +490,11 @@ BattleInfo::BattleInfo():
|
||||
setNodeType(BATTLE);
|
||||
}
|
||||
|
||||
BattleLayout BattleInfo::getLayout() const
|
||||
{
|
||||
return *layout;
|
||||
}
|
||||
|
||||
BattleID BattleInfo::getBattleID() const
|
||||
{
|
||||
return battleID;
|
||||
@ -679,12 +639,6 @@ int3 BattleInfo::getLocation() const
|
||||
return tile;
|
||||
}
|
||||
|
||||
bool BattleInfo::isCreatureBank() const
|
||||
{
|
||||
return creatureBank;
|
||||
}
|
||||
|
||||
|
||||
std::vector<SpellID> BattleInfo::getUsedSpells(BattleSide side) const
|
||||
{
|
||||
return getSide(side).usedSpellsHistory;
|
||||
|
@ -22,10 +22,12 @@ class CStack;
|
||||
class CStackInstance;
|
||||
class CStackBasicDescriptor;
|
||||
class BattleField;
|
||||
struct BattleLayout;
|
||||
|
||||
class DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallback, public IBattleState
|
||||
{
|
||||
BattleSideArray<SideInBattle> sides; //sides[0] - attacker, sides[1] - defender
|
||||
std::unique_ptr<BattleLayout> layout;
|
||||
public:
|
||||
BattleID battleID = BattleID(0);
|
||||
|
||||
@ -33,7 +35,6 @@ public:
|
||||
si32 activeStack;
|
||||
const CGTownInstance * town; //used during town siege, nullptr if this is not a siege (note that fortless town IS also a siege)
|
||||
int3 tile; //for background and bonuses
|
||||
bool creatureBank; //auxiliary field, do not serialize
|
||||
bool replayAllowed;
|
||||
std::vector<CStack*> stacks;
|
||||
std::vector<std::shared_ptr<CObstacleInstance> > obstacles;
|
||||
@ -65,6 +66,7 @@ public:
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
BattleInfo(const BattleLayout & layout);
|
||||
BattleInfo();
|
||||
virtual ~BattleInfo();
|
||||
|
||||
@ -108,7 +110,7 @@ public:
|
||||
int64_t getActualDamage(const DamageRange & damage, int32_t attackerCount, vstd::RNG & rng) const override;
|
||||
|
||||
int3 getLocation() const override;
|
||||
bool isCreatureBank() const override;
|
||||
BattleLayout getLayout() const override;
|
||||
|
||||
std::vector<SpellID> getUsedSpells(BattleSide side) const override;
|
||||
|
||||
@ -152,7 +154,7 @@ public:
|
||||
const CGHeroInstance * getHero(const PlayerColor & player) const; //returns fighting hero that belongs to given player
|
||||
|
||||
void localInit();
|
||||
static BattleInfo * setupBattle(const int3 & tile, TerrainId, const BattleField & battlefieldType, BattleSideArray<const CArmedInstance *> armies, BattleSideArray<const CGHeroInstance *> heroes, bool creatureBank, const CGTownInstance * town);
|
||||
static BattleInfo * setupBattle(const int3 & tile, TerrainId, const BattleField & battlefieldType, BattleSideArray<const CArmedInstance *> armies, BattleSideArray<const CGHeroInstance *> heroes, const BattleLayout & layout, const CGTownInstance * town);
|
||||
|
||||
BattleSide whatSide(const PlayerColor & player) const;
|
||||
|
||||
|
81
lib/battle/BattleLayout.cpp
Normal file
81
lib/battle/BattleLayout.cpp
Normal file
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* BattleLayout.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 "BattleLayout.h"
|
||||
|
||||
#include "../GameSettings.h"
|
||||
#include "../IGameCallback.h"
|
||||
#include "../VCMI_Lib.h"
|
||||
#include "../json/JsonNode.h"
|
||||
#include "../mapObjects/CArmedInstance.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
BattleLayout BattleLayout::createDefaultLayout(IGameCallback * cb, const CArmedInstance * attacker, const CArmedInstance * defender)
|
||||
{
|
||||
return createLayout(cb, "default", attacker, defender);
|
||||
}
|
||||
|
||||
BattleLayout BattleLayout::createLayout(IGameCallback * cb, const std::string & layoutName, const CArmedInstance * attacker, const CArmedInstance * defender)
|
||||
{
|
||||
const auto & loadHex = [](const JsonNode & node)
|
||||
{
|
||||
if (node.isNull())
|
||||
return BattleHex();
|
||||
else
|
||||
return BattleHex(node.Integer());
|
||||
};
|
||||
|
||||
const auto & loadUnits = [](const JsonNode & node)
|
||||
{
|
||||
UnitsArrayType::value_type result;
|
||||
for (size_t i = 0; i < GameConstants::ARMY_SIZE; ++i)
|
||||
{
|
||||
if (!node[i].isNull())
|
||||
result[i] = BattleHex(node[i].Integer());
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const JsonNode & configRoot = cb->getSettings().getValue(EGameSettings::COMBAT_LAYOUTS);
|
||||
const JsonNode & config = configRoot[layoutName];
|
||||
|
||||
BattleLayout result;
|
||||
|
||||
result.commanders[BattleSide::ATTACKER] = loadHex(config["attackerCommander"]);
|
||||
result.commanders[BattleSide::DEFENDER] = loadHex(config["defenderCommander"]);
|
||||
|
||||
for (size_t i = 0; i < 4; ++i)
|
||||
result.warMachines[BattleSide::ATTACKER][i] = loadHex(config["attackerWarMachines"][i]);
|
||||
|
||||
for (size_t i = 0; i < 4; ++i)
|
||||
result.warMachines[BattleSide::DEFENDER][i] = loadHex(config["attackerWarMachines"][i]);
|
||||
|
||||
if (attacker->formation == EArmyFormation::LOOSE && !config["attackerUnitsLoose"].isNull())
|
||||
result.units[BattleSide::ATTACKER] = loadUnits(config["attackerUnitsLoose"][attacker->stacksCount() - 1]);
|
||||
else if (attacker->formation == EArmyFormation::TIGHT && !config["attackerUnitsTight"].isNull())
|
||||
result.units[BattleSide::ATTACKER] = loadUnits(config["attackerUnitsTight"][attacker->stacksCount() - 1]);
|
||||
else
|
||||
result.units[BattleSide::ATTACKER] = loadUnits(config["attackerUnits"]);
|
||||
|
||||
if (attacker->formation == EArmyFormation::LOOSE && !config["defenderUnitsLoose"].isNull())
|
||||
result.units[BattleSide::DEFENDER] = loadUnits(config["defenderUnitsLoose"][attacker->stacksCount() - 1]);
|
||||
else if (attacker->formation == EArmyFormation::TIGHT && !config["defenderUnitsTight"].isNull())
|
||||
result.units[BattleSide::DEFENDER] = loadUnits(config["defenderUnitsTight"][attacker->stacksCount() - 1]);
|
||||
else
|
||||
result.units[BattleSide::DEFENDER] = loadUnits(config["defenderUnits"]);
|
||||
|
||||
result.obstaclesAllowed = config["obstaclesAllowed"].Bool();
|
||||
result.tacticsAllowed = config["tacticsAllowed"].Bool();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
39
lib/battle/BattleLayout.h
Normal file
39
lib/battle/BattleLayout.h
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* BattleLayout.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 "BattleHex.h"
|
||||
#include "BattleSide.h"
|
||||
#include "../constants/NumericConstants.h"
|
||||
#include "../constants/Enumerations.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class CArmedInstance;
|
||||
class IGameCallback;
|
||||
|
||||
struct DLL_EXPORT BattleLayout
|
||||
{
|
||||
using UnitsArrayType = BattleSideArray<std::array<BattleHex, GameConstants::ARMY_SIZE>>;
|
||||
using MachinesArrayType = BattleSideArray<std::array<BattleHex, 4>>;
|
||||
using CommanderArrayType = BattleSideArray<BattleHex>;
|
||||
|
||||
UnitsArrayType units;
|
||||
MachinesArrayType warMachines;
|
||||
CommanderArrayType commanders;
|
||||
|
||||
bool tacticsAllowed = false;
|
||||
bool obstaclesAllowed = false;
|
||||
|
||||
static BattleLayout createDefaultLayout(IGameCallback * cb, const CArmedInstance * attacker, const CArmedInstance * defender);
|
||||
static BattleLayout createLayout(IGameCallback * cb, const std::string & layoutName, const CArmedInstance * attacker, const CArmedInstance * defender);
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
@ -16,6 +16,7 @@ VCMI_LIB_NAMESPACE_BEGIN
|
||||
class ObstacleChanges;
|
||||
class UnitChanges;
|
||||
struct Bonus;
|
||||
struct BattleLayout;
|
||||
class JsonNode;
|
||||
class JsonSerializeFormat;
|
||||
class BattleField;
|
||||
@ -72,7 +73,7 @@ public:
|
||||
virtual int64_t getActualDamage(const DamageRange & damage, int32_t attackerCount, vstd::RNG & rng) const = 0;
|
||||
|
||||
virtual int3 getLocation() const = 0;
|
||||
virtual bool isCreatureBank() const = 0;
|
||||
virtual BattleLayout getLayout() const = 0;
|
||||
};
|
||||
|
||||
class DLL_LINKAGE IBattleState : public IBattleInfo
|
||||
|
@ -145,7 +145,7 @@ JsonNode ArmyMovementUpdater::toJsonNode() const
|
||||
}
|
||||
std::shared_ptr<Bonus> TimesStackLevelUpdater::createUpdatedBonus(const std::shared_ptr<Bonus> & b, const CBonusSystemNode & context) const
|
||||
{
|
||||
if(context.getNodeType() == CBonusSystemNode::STACK_INSTANCE)
|
||||
if(context.getNodeType() == CBonusSystemNode::STACK_INSTANCE || context.getNodeType() == CBonusSystemNode::COMMANDER)
|
||||
{
|
||||
int level = dynamic_cast<const CStackInstance &>(context).getLevel();
|
||||
auto newBonus = std::make_shared<Bonus>(*b);
|
||||
@ -155,8 +155,7 @@ std::shared_ptr<Bonus> TimesStackLevelUpdater::createUpdatedBonus(const std::sha
|
||||
else if(context.getNodeType() == CBonusSystemNode::STACK_BATTLE)
|
||||
{
|
||||
const auto & stack = dynamic_cast<const CStack &>(context);
|
||||
//only update if stack doesn't have an instance (summons, war machines)
|
||||
//otherwise we'd end up multiplying twice
|
||||
//update if stack doesn't have an instance (summons, war machines)
|
||||
if(stack.base == nullptr)
|
||||
{
|
||||
int level = stack.unitType()->getLevel();
|
||||
@ -164,6 +163,14 @@ std::shared_ptr<Bonus> TimesStackLevelUpdater::createUpdatedBonus(const std::sha
|
||||
newBonus->val *= level;
|
||||
return newBonus;
|
||||
}
|
||||
// If these are not handled here, the final outcome may potentially be incorrect.
|
||||
else
|
||||
{
|
||||
int level = dynamic_cast<const CStackInstance*>(stack.base)->getLevel();
|
||||
auto newBonus = std::make_shared<Bonus>(*b);
|
||||
newBonus->val *= level;
|
||||
return newBonus;
|
||||
}
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
@ -169,8 +169,9 @@ void CampaignHandler::readHeaderFromJson(CampaignHeader & ret, JsonNode & reader
|
||||
ret.modName = modName;
|
||||
ret.encoding = encoding;
|
||||
ret.loadingBackground = ImagePath::fromJson(reader["loadingBackground"]);
|
||||
ret.introVideoRim = ImagePath::fromJson(reader["introVideoRim"]);
|
||||
ret.videoRim = ImagePath::fromJson(reader["videoRim"]);
|
||||
ret.introVideo = VideoPath::fromJson(reader["introVideo"]);
|
||||
ret.outroVideo = VideoPath::fromJson(reader["outroVideo"]);
|
||||
}
|
||||
|
||||
CampaignScenario CampaignHandler::readScenarioFromJson(JsonNode & reader)
|
||||
|
@ -210,9 +210,9 @@ ImagePath CampaignHeader::getLoadingBackground() const
|
||||
return loadingBackground;
|
||||
}
|
||||
|
||||
ImagePath CampaignHeader::getIntroVideoRim() const
|
||||
ImagePath CampaignHeader::getVideoRim() const
|
||||
{
|
||||
return introVideoRim;
|
||||
return videoRim;
|
||||
}
|
||||
|
||||
VideoPath CampaignHeader::getIntroVideo() const
|
||||
@ -220,6 +220,11 @@ VideoPath CampaignHeader::getIntroVideo() const
|
||||
return introVideo;
|
||||
}
|
||||
|
||||
VideoPath CampaignHeader::getOutroVideo() const
|
||||
{
|
||||
return outroVideo;
|
||||
}
|
||||
|
||||
const CampaignRegions & CampaignHeader::getRegions() const
|
||||
{
|
||||
return campaignRegions;
|
||||
@ -490,10 +495,12 @@ void Campaign::overrideCampaign()
|
||||
loadLegacyData(CampaignRegions::fromJson(entry.second["regions"]), entry.second["scenarioCount"].Integer());
|
||||
if(!entry.second["loadingBackground"].isNull())
|
||||
loadingBackground = ImagePath::builtin(entry.second["loadingBackground"].String());
|
||||
if(!entry.second["introVideoRim"].isNull())
|
||||
introVideoRim = ImagePath::builtin(entry.second["introVideoRim"].String());
|
||||
if(!entry.second["videoRim"].isNull())
|
||||
videoRim = ImagePath::builtin(entry.second["videoRim"].String());
|
||||
if(!entry.second["introVideo"].isNull())
|
||||
introVideo = VideoPath::builtin(entry.second["introVideo"].String());
|
||||
if(!entry.second["outroVideo"].isNull())
|
||||
outroVideo = VideoPath::builtin(entry.second["outroVideo"].String());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,8 +98,9 @@ class DLL_LINKAGE CampaignHeader : public boost::noncopyable
|
||||
std::string modName;
|
||||
std::string encoding;
|
||||
ImagePath loadingBackground;
|
||||
ImagePath introVideoRim;
|
||||
ImagePath videoRim;
|
||||
VideoPath introVideo;
|
||||
VideoPath outroVideo;
|
||||
|
||||
int numberOfScenarios = 0;
|
||||
bool difficultyChosenByPlayer = false;
|
||||
@ -124,8 +125,9 @@ public:
|
||||
std::string getEncoding() const;
|
||||
AudioPath getMusic() const;
|
||||
ImagePath getLoadingBackground() const;
|
||||
ImagePath getIntroVideoRim() const;
|
||||
ImagePath getVideoRim() const;
|
||||
VideoPath getIntroVideo() const;
|
||||
VideoPath getOutroVideo() const;
|
||||
|
||||
const CampaignRegions & getRegions() const;
|
||||
TextContainerRegistrable & getTexts();
|
||||
@ -153,9 +155,11 @@ public:
|
||||
if (h.version >= Handler::Version::CHRONICLES_SUPPORT)
|
||||
{
|
||||
h & loadingBackground;
|
||||
h & introVideoRim;
|
||||
h & videoRim;
|
||||
h & introVideo;
|
||||
}
|
||||
if (h.version >= Handler::Version::CAMPAIGN_OUTRO_SUPPORT)
|
||||
h & outroVideo;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -62,17 +62,20 @@ Rewardable::Configuration CRewardableConstructor::generateConfiguration(IGameCal
|
||||
|
||||
void CRewardableConstructor::configureObject(CGObjectInstance * object, vstd::RNG & rng) const
|
||||
{
|
||||
if(auto * rewardableObject = dynamic_cast<CRewardableObject*>(object))
|
||||
{
|
||||
rewardableObject->configuration = generateConfiguration(object->cb, rng, object->ID);
|
||||
auto * rewardableObject = dynamic_cast<CRewardableObject*>(object);
|
||||
|
||||
if (rewardableObject->configuration.info.empty())
|
||||
{
|
||||
if (objectInfo.getParameters()["rewards"].isNull())
|
||||
logMod->error("Object %s has invalid configuration! No defined rewards found!", getJsonKey());
|
||||
else
|
||||
logMod->error("Object %s has invalid configuration! Make sure that defined appear chances are continuous!", getJsonKey());
|
||||
}
|
||||
if (!rewardableObject)
|
||||
throw std::runtime_error("Object " + std::to_string(object->getObjGroupIndex()) + ", " + std::to_string(object->getObjTypeIndex()) + " is not a rewardable object!" );
|
||||
|
||||
rewardableObject->configuration = generateConfiguration(object->cb, rng, object->ID);
|
||||
rewardableObject->initializeGuards();
|
||||
|
||||
if (rewardableObject->configuration.info.empty())
|
||||
{
|
||||
if (objectInfo.getParameters()["rewards"].isNull())
|
||||
logMod->error("Object %s has invalid configuration! No defined rewards found!", getJsonKey());
|
||||
else
|
||||
logMod->error("Object %s has invalid configuration! Make sure that defined appear chances are continuous!", getJsonKey());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,6 @@ class CGHeroInstance;
|
||||
class CGMarket;
|
||||
class CHeroClass;
|
||||
class CGCreature;
|
||||
class CBank;
|
||||
class CGBoat;
|
||||
class CFaction;
|
||||
class CStackBasicDescriptor;
|
||||
|
@ -15,7 +15,6 @@
|
||||
#include <vcmi/spells/Service.h>
|
||||
|
||||
#include "../texts/CGeneralTextHandler.h"
|
||||
#include "../CSoundBase.h"
|
||||
#include "../IGameSettings.h"
|
||||
#include "../CPlayerState.h"
|
||||
#include "../mapObjectConstructors/CObjectClassesHandler.h"
|
||||
@ -138,140 +137,31 @@ bool CBank::wasVisited (PlayerColor player) const
|
||||
|
||||
void CBank::onHeroVisit(const CGHeroInstance * h) const
|
||||
{
|
||||
ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_TEAM, id, h->id);
|
||||
ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_PLAYER, id, h->id);
|
||||
cb->sendAndApply(&cov);
|
||||
|
||||
if(!bankConfig && (ID.toEnum() == Obj::CREATURE_BANK || ID.toEnum() == Obj::DRAGON_UTOPIA))
|
||||
{
|
||||
blockingDialogAnswered(h, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
int banktext = 0;
|
||||
switch (ID.toEnum())
|
||||
{
|
||||
case Obj::DERELICT_SHIP:
|
||||
banktext = 41;
|
||||
break;
|
||||
case Obj::DRAGON_UTOPIA:
|
||||
banktext = 47;
|
||||
break;
|
||||
case Obj::CRYPT:
|
||||
banktext = 119;
|
||||
break;
|
||||
case Obj::SHIPWRECK:
|
||||
banktext = 122;
|
||||
break;
|
||||
case Obj::PYRAMID:
|
||||
banktext = 105;
|
||||
break;
|
||||
case Obj::CREATURE_BANK:
|
||||
default:
|
||||
banktext = 32;
|
||||
break;
|
||||
}
|
||||
BlockingDialog bd(true, false);
|
||||
bd.player = h->getOwner();
|
||||
bd.soundID = soundBase::invalid; // Sound is handled in json files, else two sounds are played
|
||||
bd.text.appendLocalString(EMetaText::ADVOB_TXT, banktext);
|
||||
bd.text.appendLocalString(EMetaText::ADVOB_TXT, 32);
|
||||
bd.components = getPopupComponents(h->getOwner());
|
||||
if (banktext == 32)
|
||||
bd.text.replaceRawString(getObjectName());
|
||||
|
||||
bd.text.replaceRawString(getObjectName());
|
||||
cb->showBlockingDialog(this, &bd);
|
||||
}
|
||||
|
||||
void CBank::doVisit(const CGHeroInstance * hero) const
|
||||
{
|
||||
int textID = -1;
|
||||
InfoWindow iw;
|
||||
iw.type = EInfoWindowMode::AUTO;
|
||||
iw.player = hero->getOwner();
|
||||
MetaString loot;
|
||||
|
||||
if (bankConfig)
|
||||
if (!bankConfig)
|
||||
{
|
||||
switch (ID.toEnum())
|
||||
{
|
||||
case Obj::DERELICT_SHIP:
|
||||
textID = 43;
|
||||
break;
|
||||
case Obj::CRYPT:
|
||||
textID = 121;
|
||||
break;
|
||||
case Obj::SHIPWRECK:
|
||||
textID = 124;
|
||||
break;
|
||||
case Obj::PYRAMID:
|
||||
textID = 106;
|
||||
break;
|
||||
case Obj::CREATURE_BANK:
|
||||
case Obj::DRAGON_UTOPIA:
|
||||
default:
|
||||
textID = 34;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (ID.toEnum())
|
||||
{
|
||||
case Obj::SHIPWRECK:
|
||||
case Obj::DERELICT_SHIP:
|
||||
case Obj::CRYPT:
|
||||
{
|
||||
GiveBonus gbonus;
|
||||
gbonus.id = hero->id;
|
||||
gbonus.bonus.duration = BonusDuration::ONE_BATTLE;
|
||||
gbonus.bonus.source = BonusSource::OBJECT_TYPE;
|
||||
gbonus.bonus.sid = BonusSourceID(ID);
|
||||
gbonus.bonus.type = BonusType::MORALE;
|
||||
gbonus.bonus.val = -1;
|
||||
switch (ID.toEnum())
|
||||
{
|
||||
case Obj::SHIPWRECK:
|
||||
textID = 123;
|
||||
gbonus.bonus.description = MetaString::createFromTextID("core.arraytxt.99");
|
||||
break;
|
||||
case Obj::DERELICT_SHIP:
|
||||
textID = 42;
|
||||
gbonus.bonus.description = MetaString::createFromTextID("core.arraytxt.101");
|
||||
break;
|
||||
case Obj::CRYPT:
|
||||
textID = 120;
|
||||
gbonus.bonus.description = MetaString::createFromTextID("core.arraytxt.98");
|
||||
break;
|
||||
}
|
||||
cb->giveHeroBonus(&gbonus);
|
||||
iw.components.emplace_back(ComponentType::MORALE, -1);
|
||||
iw.soundID = soundBase::invalid;
|
||||
break;
|
||||
}
|
||||
case Obj::PYRAMID:
|
||||
{
|
||||
GiveBonus gb;
|
||||
gb.bonus = Bonus(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT_INSTANCE, -2, BonusSourceID(id));
|
||||
gb.bonus.description = MetaString::createFromTextID("core.arraytxt.70");
|
||||
gb.id = hero->id;
|
||||
cb->giveHeroBonus(&gb);
|
||||
textID = 107;
|
||||
iw.components.emplace_back(ComponentType::LUCK, -2);
|
||||
break;
|
||||
}
|
||||
case Obj::CREATURE_BANK:
|
||||
case Obj::DRAGON_UTOPIA:
|
||||
default:
|
||||
iw.text.appendRawString(VLC->generaltexth->advobtxt[33]);// This was X, now is completely empty
|
||||
iw.text.replaceRawString(getObjectName());
|
||||
}
|
||||
if(textID != -1)
|
||||
{
|
||||
iw.text.appendLocalString(EMetaText::ADVOB_TXT, textID);
|
||||
}
|
||||
iw.text.appendRawString(VLC->generaltexth->advobtxt[33]);// This was X, now is completely empty
|
||||
iw.text.replaceRawString(getObjectName());
|
||||
cb->showInfoDialog(&iw);
|
||||
}
|
||||
|
||||
|
||||
//grant resources
|
||||
if (bankConfig)
|
||||
{
|
||||
@ -297,17 +187,15 @@ void CBank::doVisit(const CGHeroInstance * hero) const
|
||||
//display loot
|
||||
if (!iw.components.empty())
|
||||
{
|
||||
iw.text.appendLocalString(EMetaText::ADVOB_TXT, textID);
|
||||
if (textID == 34)
|
||||
iw.text.appendLocalString(EMetaText::ADVOB_TXT, 34);
|
||||
const auto * strongest = boost::range::max_element(bankConfig->guards, [](const CStackBasicDescriptor & a, const CStackBasicDescriptor & b)
|
||||
{
|
||||
const auto * strongest = boost::range::max_element(bankConfig->guards, [](const CStackBasicDescriptor & a, const CStackBasicDescriptor & b)
|
||||
{
|
||||
return a.type->getFightValue() < b.type->getFightValue();
|
||||
})->type;
|
||||
return a.type->getFightValue() < b.type->getFightValue();
|
||||
})->type;
|
||||
|
||||
iw.text.replaceNamePlural(strongest->getId());
|
||||
iw.text.replaceRawString(loot.buildList());
|
||||
|
||||
iw.text.replaceNamePlural(strongest->getId());
|
||||
iw.text.replaceRawString(loot.buildList());
|
||||
}
|
||||
cb->showInfoDialog(&iw);
|
||||
}
|
||||
|
||||
@ -320,10 +208,7 @@ void CBank::doVisit(const CGHeroInstance * hero) const
|
||||
std::set<SpellID> spells;
|
||||
|
||||
bool noWisdom = false;
|
||||
if(textID == 106)
|
||||
{
|
||||
iw.text.appendLocalString(EMetaText::ADVOB_TXT, textID); //pyramid
|
||||
}
|
||||
|
||||
for(const SpellID & spellId : bankConfig->spells)
|
||||
{
|
||||
const auto * spell = spellId.toEntity(VLC);
|
||||
@ -398,7 +283,7 @@ void CBank::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) c
|
||||
if (answer)
|
||||
{
|
||||
if (bankConfig) // not looted bank
|
||||
cb->startBattleI(hero, this, !regularUnitPlacement);
|
||||
cb->startBattle(hero, this);
|
||||
else
|
||||
doVisit(hero);
|
||||
}
|
||||
|
@ -465,7 +465,7 @@ void CGCreature::fight( const CGHeroInstance *h ) const
|
||||
}
|
||||
}
|
||||
|
||||
cb->startBattleI(h, this);
|
||||
cb->startBattle(h, this);
|
||||
|
||||
}
|
||||
|
||||
|
@ -505,7 +505,7 @@ void CGDwelling::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answ
|
||||
if(stacksCount() > 0 && relations == PlayerRelations::ENEMIES) //guards present
|
||||
{
|
||||
if(answer)
|
||||
cb->startBattleI(hero, this);
|
||||
cb->startBattle(hero, this);
|
||||
}
|
||||
else if(answer)
|
||||
{
|
||||
|
@ -512,7 +512,7 @@ void CGHeroInstance::onHeroVisit(const CGHeroInstance * h) const
|
||||
if(visitedTown) //we're in town
|
||||
visitedTown->onHeroVisit(h); //town will handle attacking
|
||||
else
|
||||
cb->startBattleI(h, this);
|
||||
cb->startBattle(h, this);
|
||||
}
|
||||
}
|
||||
else if(ID == Obj::PRISON)
|
||||
|
@ -193,7 +193,7 @@ void CGPandoraBox::blockingDialogAnswered(const CGHeroInstance *hero, int32_t an
|
||||
if(stacksCount() > 0) //if pandora's box is protected by army
|
||||
{
|
||||
hero->showInfoDialog(16, 0, EInfoWindowMode::MODAL);
|
||||
cb->startBattleI(hero, this); //grants things after battle
|
||||
cb->startBattle(hero, this); //grants things after battle
|
||||
}
|
||||
else if(getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT).empty())
|
||||
{
|
||||
@ -332,7 +332,7 @@ void CGEvent::activated( const CGHeroInstance * h ) const
|
||||
else
|
||||
iw.text.appendLocalString(EMetaText::ADVOB_TXT, 16);
|
||||
cb->showInfoDialog(&iw);
|
||||
cb->startBattleI(h, this);
|
||||
cb->startBattle(h, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "../spells/CSpellHandler.h"
|
||||
#include "../bonuses/Bonus.h"
|
||||
#include "../battle/IBattleInfoCallback.h"
|
||||
#include "../battle/BattleLayout.h"
|
||||
#include "../CConfigHandler.h"
|
||||
#include "../texts/CGeneralTextHandler.h"
|
||||
#include "../IGameCallback.h"
|
||||
@ -321,7 +322,7 @@ void CGTownInstance::onHeroVisit(const CGHeroInstance * h) const
|
||||
|
||||
const_cast<CGHeroInstance *>(defendingHero)->inTownGarrison = false; //hack to return visitor from garrison after battle
|
||||
}
|
||||
cb->startBattlePrimary(h, defendingArmy, getSightCenter(), h, defendingHero, false, (isBattleOutside ? nullptr : this));
|
||||
cb->startBattle(h, defendingArmy, getSightCenter(), h, defendingHero, BattleLayout::createDefaultLayout(cb, h, defendingArmy), (isBattleOutside ? nullptr : this));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -10,16 +10,20 @@
|
||||
|
||||
#include "StdInc.h"
|
||||
#include "CRewardableObject.h"
|
||||
#include "../gameState/CGameState.h"
|
||||
#include "../texts/CGeneralTextHandler.h"
|
||||
|
||||
#include "../CPlayerState.h"
|
||||
#include "../GameSettings.h"
|
||||
#include "../IGameCallback.h"
|
||||
#include "../battle/BattleLayout.h"
|
||||
#include "../gameState/CGameState.h"
|
||||
#include "../mapObjectConstructors/AObjectTypeHandler.h"
|
||||
#include "../mapObjectConstructors/CObjectClassesHandler.h"
|
||||
#include "../mapObjectConstructors/CRewardableConstructor.h"
|
||||
#include "../mapObjects/CGHeroInstance.h"
|
||||
#include "../networkPacks/PacksForClient.h"
|
||||
#include "../networkPacks/PacksForClientBattle.h"
|
||||
#include "../serializer/JsonSerializeFormat.h"
|
||||
#include "../texts/CGeneralTextHandler.h"
|
||||
|
||||
#include <vstd/RNG.h>
|
||||
|
||||
@ -91,7 +95,48 @@ std::vector<Component> CRewardableObject::loadComponents(const CGHeroInstance *
|
||||
return result;
|
||||
}
|
||||
|
||||
void CRewardableObject::onHeroVisit(const CGHeroInstance *h) const
|
||||
bool CRewardableObject::guardedPotentially() const
|
||||
{
|
||||
for (auto const & visitInfo : configuration.info)
|
||||
if (!visitInfo.reward.guards.empty())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CRewardableObject::guardedPresently() const
|
||||
{
|
||||
return stacksCount() > 0;
|
||||
}
|
||||
|
||||
void CRewardableObject::onHeroVisit(const CGHeroInstance *hero) const
|
||||
{
|
||||
if(!wasScouted(hero->getOwner()))
|
||||
{
|
||||
ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_SCOUTED, id, hero->id);
|
||||
cb->sendAndApply(&cov);
|
||||
}
|
||||
|
||||
if (guardedPresently())
|
||||
{
|
||||
auto guardedIndexes = getAvailableRewards(hero, Rewardable::EEventType::EVENT_GUARDED);
|
||||
auto guardedReward = configuration.info.at(guardedIndexes.at(0));
|
||||
|
||||
// ask player to confirm attack
|
||||
BlockingDialog bd(true, false);
|
||||
bd.player = hero->getOwner();
|
||||
bd.text = guardedReward.message;
|
||||
bd.components = getPopupComponents(hero->getOwner());
|
||||
|
||||
cb->showBlockingDialog(this, &bd);
|
||||
}
|
||||
else
|
||||
{
|
||||
doHeroVisit(hero);
|
||||
}
|
||||
}
|
||||
|
||||
void CRewardableObject::doHeroVisit(const CGHeroInstance *h) const
|
||||
{
|
||||
if(!wasVisitedBefore(h))
|
||||
{
|
||||
@ -154,7 +199,7 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *h) const
|
||||
|
||||
if(!objectRemovalPossible && getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT).empty())
|
||||
{
|
||||
ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_TEAM, id, h->id);
|
||||
ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_PLAYER, id, h->id);
|
||||
cb->sendAndApply(&cov);
|
||||
}
|
||||
}
|
||||
@ -164,7 +209,7 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *h) const
|
||||
|
||||
if (!wasVisited(h->getOwner()))
|
||||
{
|
||||
ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_TEAM, id, h->id);
|
||||
ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_PLAYER, id, h->id);
|
||||
cb->sendAndApply(&cov);
|
||||
}
|
||||
|
||||
@ -181,39 +226,36 @@ void CRewardableObject::heroLevelUpDone(const CGHeroInstance *hero) const
|
||||
grantRewardAfterLevelup(cb, configuration.info.at(selectedReward), this, hero);
|
||||
}
|
||||
|
||||
void CRewardableObject::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const
|
||||
void CRewardableObject::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
|
||||
{
|
||||
if(answer == 0)
|
||||
if (result.winner == BattleSide::ATTACKER)
|
||||
{
|
||||
switch (configuration.visitMode)
|
||||
{
|
||||
case Rewardable::VISIT_UNLIMITED:
|
||||
case Rewardable::VISIT_BONUS:
|
||||
case Rewardable::VISIT_HERO:
|
||||
case Rewardable::VISIT_LIMITER:
|
||||
{
|
||||
// workaround for object with refusable reward not getting marked as visited
|
||||
// TODO: better solution that would also work for player-visitable objects
|
||||
if (!wasScouted(hero->getOwner()))
|
||||
{
|
||||
ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_TEAM, id, hero->id);
|
||||
cb->sendAndApply(&cov);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return; // player refused
|
||||
doHeroVisit(hero);
|
||||
}
|
||||
}
|
||||
|
||||
if(answer > 0 && answer-1 < configuration.info.size())
|
||||
void CRewardableObject::blockingDialogAnswered(const CGHeroInstance * hero, int32_t answer) const
|
||||
{
|
||||
if(guardedPresently())
|
||||
{
|
||||
auto list = getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT);
|
||||
markAsVisited(hero);
|
||||
grantReward(list[answer - 1], hero);
|
||||
if (answer)
|
||||
{
|
||||
auto layout = BattleLayout::createLayout(cb, configuration.guardsLayout, hero, this);
|
||||
cb->startBattle(hero, this, visitablePos(), hero, nullptr, layout, nullptr);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("Unhandled choice");
|
||||
if(answer > 0 && answer - 1 < configuration.info.size())
|
||||
{
|
||||
auto list = getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT);
|
||||
markAsVisited(hero);
|
||||
grantReward(list[answer - 1], hero);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("Unhandled choice");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,7 +263,7 @@ void CRewardableObject::markAsVisited(const CGHeroInstance * hero) const
|
||||
{
|
||||
cb->setObjPropertyValue(id, ObjProperty::REWARD_CLEARED, true);
|
||||
|
||||
ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD, id, hero->id);
|
||||
ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_HERO, id, hero->id);
|
||||
cb->sendAndApply(&cov);
|
||||
}
|
||||
|
||||
@ -277,7 +319,7 @@ bool CRewardableObject::wasVisited(PlayerColor player) const
|
||||
|
||||
bool CRewardableObject::wasScouted(PlayerColor player) const
|
||||
{
|
||||
return vstd::contains(cb->getPlayerState(player)->visitedObjects, ObjectInstanceID(id));
|
||||
return vstd::contains(cb->getPlayerTeam(player)->scoutedObjects, ObjectInstanceID(id));
|
||||
}
|
||||
|
||||
bool CRewardableObject::wasVisited(const CGHeroInstance * h) const
|
||||
@ -365,22 +407,44 @@ std::vector<Component> CRewardableObject::getPopupComponentsImpl(PlayerColor pla
|
||||
if (!wasScouted(player))
|
||||
return {};
|
||||
|
||||
if (!configuration.showScoutedPreview)
|
||||
return {};
|
||||
|
||||
auto rewardIndices = getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT);
|
||||
if (rewardIndices.empty() && !configuration.info.empty())
|
||||
if (guardedPresently())
|
||||
{
|
||||
// Object has valid config, but current hero has no rewards that he can receive.
|
||||
// Usually this happens if hero has already visited this object -> show reward using context without any hero
|
||||
// since reward may be context-sensitive - e.g. Witch Hut that gives 1 skill, but always at basic level
|
||||
return loadComponents(nullptr, {0});
|
||||
if (!cb->getSettings().getBoolean(EGameSettings::BANKS_SHOW_GUARDS_COMPOSITION))
|
||||
return {};
|
||||
|
||||
std::map<CreatureID, int> guardsAmounts;
|
||||
std::vector<Component> result;
|
||||
|
||||
for (auto const & slot : Slots())
|
||||
if (slot.second)
|
||||
guardsAmounts[slot.second->getCreatureID()] += slot.second->getCount();
|
||||
|
||||
for (auto const & guard : guardsAmounts)
|
||||
{
|
||||
Component comp(ComponentType::CREATURE, guard.first, guard.second);
|
||||
result.push_back(comp);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!configuration.showScoutedPreview)
|
||||
return {};
|
||||
|
||||
if (rewardIndices.empty())
|
||||
return {};
|
||||
auto rewardIndices = getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT);
|
||||
if (rewardIndices.empty() && !configuration.info.empty())
|
||||
{
|
||||
// Object has valid config, but current hero has no rewards that he can receive.
|
||||
// Usually this happens if hero has already visited this object -> show reward using context without any hero
|
||||
// since reward may be context-sensitive - e.g. Witch Hut that gives 1 skill, but always at basic level
|
||||
return loadComponents(nullptr, {0});
|
||||
}
|
||||
|
||||
return loadComponents(hero, rewardIndices);
|
||||
if (rewardIndices.empty())
|
||||
return {};
|
||||
|
||||
return loadComponents(hero, rewardIndices);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Component> CRewardableObject::getPopupComponents(PlayerColor player) const
|
||||
@ -440,4 +504,31 @@ void CRewardableObject::serializeJsonOptions(JsonSerializeFormat & handler)
|
||||
handler.serializeStruct("rewardable", static_cast<Rewardable::Interface&>(*this));
|
||||
}
|
||||
|
||||
void CRewardableObject::initializeGuards()
|
||||
{
|
||||
clearSlots();
|
||||
|
||||
// Workaround for default creature banks strings that has placeholder for object name
|
||||
// TODO: find better location for this code
|
||||
for (auto & visitInfo : configuration.info)
|
||||
visitInfo.message.replaceRawString(getObjectName());
|
||||
|
||||
for (auto const & visitInfo : configuration.info)
|
||||
{
|
||||
for (auto const & guard : visitInfo.reward.guards)
|
||||
{
|
||||
auto slotID = getFreeSlot();
|
||||
if (!slotID.validSlot())
|
||||
return;
|
||||
|
||||
putStack(slotID, new CStackInstance(guard.getId(), guard.getCount()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CRewardableObject::isCoastVisitable() const
|
||||
{
|
||||
return configuration.coastVisitable;
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
@ -45,7 +45,14 @@ protected:
|
||||
std::string getDescriptionMessage(PlayerColor player, const CGHeroInstance * hero) const;
|
||||
std::vector<Component> getPopupComponentsImpl(PlayerColor player, const CGHeroInstance * hero) const;
|
||||
|
||||
void doHeroVisit(const CGHeroInstance *h) const;
|
||||
|
||||
/// Returns true if this object might have guards present, whether they were cleared or not
|
||||
bool guardedPotentially() const;
|
||||
/// Returns true if this object is currently guarded
|
||||
bool guardedPresently() const;
|
||||
public:
|
||||
|
||||
/// Visitability checks. Note that hero check includes check for hero owner (returns true if object was visited by player)
|
||||
bool wasVisited(PlayerColor player) const override;
|
||||
bool wasVisited(const CGHeroInstance * h) const override;
|
||||
@ -56,6 +63,8 @@ public:
|
||||
/// gives reward to player or ask for choice in case of multiple rewards
|
||||
void onHeroVisit(const CGHeroInstance *h) const override;
|
||||
|
||||
void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override;
|
||||
|
||||
///possibly resets object state
|
||||
void newTurn(vstd::RNG & rand) const override;
|
||||
|
||||
@ -66,6 +75,10 @@ public:
|
||||
void blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const override;
|
||||
|
||||
void initObj(vstd::RNG & rand) override;
|
||||
|
||||
bool isCoastVisitable() const override;
|
||||
|
||||
void initializeGuards();
|
||||
|
||||
void setPropertyDer(ObjProperty what, ObjPropertyID identifier) override;
|
||||
|
||||
@ -89,13 +102,6 @@ public:
|
||||
};
|
||||
|
||||
//TODO:
|
||||
|
||||
// MAX
|
||||
// class DLL_LINKAGE CBank : public CArmedInstance
|
||||
// class DLL_LINKAGE CGPyramid : public CBank
|
||||
|
||||
// EXTRA
|
||||
// class DLL_LINKAGE CTownBonus : public CGTownBuilding
|
||||
// class DLL_LINKAGE CGKeys : public CGObjectInstance //Base class for Keymaster and guards
|
||||
// class DLL_LINKAGE CGKeymasterTent : public CGKeys
|
||||
// class DLL_LINKAGE CGBorderGuard : public CGKeys, public IQuestObject
|
||||
|
0
lib/mapObjects/CreatureBank.cpp
Normal file
0
lib/mapObjects/CreatureBank.cpp
Normal file
0
lib/mapObjects/CreatureBank.h
Normal file
0
lib/mapObjects/CreatureBank.h
Normal file
@ -14,7 +14,6 @@
|
||||
#include "CObjectHandler.h"
|
||||
|
||||
#include "CArmedInstance.h"
|
||||
#include "CBank.h"
|
||||
#include "CGDwelling.h"
|
||||
#include "CGHeroInstance.h"
|
||||
#include "CGMarket.h"
|
||||
|
@ -219,7 +219,7 @@ void CGMine::battleFinished(const CGHeroInstance *hero, const BattleResult &resu
|
||||
void CGMine::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const
|
||||
{
|
||||
if(answer)
|
||||
cb->startBattleI(hero, this);
|
||||
cb->startBattle(hero, this);
|
||||
}
|
||||
|
||||
void CGMine::serializeJsonOptions(JsonSerializeFormat & handler)
|
||||
@ -352,7 +352,7 @@ void CGResource::battleFinished(const CGHeroInstance *hero, const BattleResult &
|
||||
void CGResource::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const
|
||||
{
|
||||
if(answer)
|
||||
cb->startBattleI(hero, this);
|
||||
cb->startBattle(hero, this);
|
||||
}
|
||||
|
||||
void CGResource::serializeJsonOptions(JsonSerializeFormat & handler)
|
||||
@ -919,7 +919,7 @@ void CGArtifact::battleFinished(const CGHeroInstance *hero, const BattleResult &
|
||||
void CGArtifact::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const
|
||||
{
|
||||
if(answer)
|
||||
cb->startBattleI(hero, this);
|
||||
cb->startBattle(hero, this);
|
||||
}
|
||||
|
||||
void CGArtifact::afterAddToMap(CMap * map)
|
||||
@ -999,7 +999,7 @@ void CGGarrison::onHeroVisit (const CGHeroInstance *h) const
|
||||
auto relations = cb->gameState()->getPlayerRelations(h->tempOwner, tempOwner);
|
||||
if (relations == PlayerRelations::ENEMIES && stacksCount() > 0) {
|
||||
//TODO: Find a way to apply magic garrison effects in battle.
|
||||
cb->startBattleI(h, this);
|
||||
cb->startBattle(h, this);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1459,7 +1459,7 @@ CGObjectInstance * CMapLoaderH3M::readGeneric(const int3 & mapPosition, std::sha
|
||||
CGObjectInstance * CMapLoaderH3M::readPyramid(const int3 & mapPosition, std::shared_ptr<const ObjectTemplate> objectTemplate)
|
||||
{
|
||||
if(objectTemplate->subid == 0)
|
||||
return new CBank(map->cb);
|
||||
return readGeneric(mapPosition, objectTemplate);
|
||||
|
||||
return new CGObjectInstance(map->cb);
|
||||
}
|
||||
|
@ -1035,32 +1035,32 @@ void ChangeObjPos::applyGs(CGameState *gs)
|
||||
void ChangeObjectVisitors::applyGs(CGameState *gs)
|
||||
{
|
||||
switch (mode) {
|
||||
case VISITOR_ADD:
|
||||
case VISITOR_ADD_HERO:
|
||||
gs->getPlayerTeam(gs->getHero(hero)->tempOwner)->scoutedObjects.insert(object);
|
||||
gs->getHero(hero)->visitedObjects.insert(object);
|
||||
gs->getPlayerState(gs->getHero(hero)->tempOwner)->visitedObjects.insert(object);
|
||||
break;
|
||||
case VISITOR_ADD_TEAM:
|
||||
{
|
||||
TeamState *ts = gs->getPlayerTeam(gs->getHero(hero)->tempOwner);
|
||||
for(const auto & color : ts->players)
|
||||
{
|
||||
gs->getPlayerState(color)->visitedObjects.insert(object);
|
||||
}
|
||||
}
|
||||
case VISITOR_ADD_PLAYER:
|
||||
gs->getPlayerTeam(gs->getHero(hero)->tempOwner)->scoutedObjects.insert(object);
|
||||
for(const auto & color : gs->getPlayerTeam(gs->getHero(hero)->tempOwner)->players)
|
||||
gs->getPlayerState(color)->visitedObjects.insert(object);
|
||||
|
||||
break;
|
||||
case VISITOR_CLEAR:
|
||||
// remove visit info from all heroes, including those that are not present on map
|
||||
for (CGHeroInstance * hero : gs->map->allHeroes)
|
||||
{
|
||||
if (hero)
|
||||
{
|
||||
hero->visitedObjects.erase(object); // remove visit info from all heroes, including those that are not present on map
|
||||
}
|
||||
}
|
||||
hero->visitedObjects.erase(object);
|
||||
|
||||
for(auto &elem : gs->players)
|
||||
{
|
||||
elem.second.visitedObjects.erase(object);
|
||||
}
|
||||
|
||||
for(auto &elem : gs->teams)
|
||||
elem.second.scoutedObjects.erase(object);
|
||||
|
||||
break;
|
||||
case VISITOR_SCOUTED:
|
||||
gs->getPlayerTeam(gs->getHero(hero)->tempOwner)->scoutedObjects.insert(object);
|
||||
|
||||
break;
|
||||
case VISITOR_GLOBAL:
|
||||
@ -1069,9 +1069,6 @@ void ChangeObjectVisitors::applyGs(CGameState *gs)
|
||||
gs->getPlayerState(gs->getHero(hero)->tempOwner)->visitedObjectsGlobal.insert({objectPtr->ID, objectPtr->subID});
|
||||
break;
|
||||
}
|
||||
case VISITOR_REMOVE:
|
||||
gs->getHero(hero)->visitedObjects.erase(object);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2434,6 +2431,7 @@ void SetRewardableConfiguration::applyGs(CGameState *gs)
|
||||
auto * rewardablePtr = dynamic_cast<CRewardableObject *>(objectPtr);
|
||||
assert(rewardablePtr);
|
||||
rewardablePtr->configuration = configuration;
|
||||
rewardablePtr->initializeGuards();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1216,11 +1216,11 @@ struct DLL_LINKAGE ChangeObjectVisitors : public CPackForClient
|
||||
{
|
||||
enum VisitMode
|
||||
{
|
||||
VISITOR_ADD, // mark hero as one that have visited this object
|
||||
VISITOR_ADD_TEAM, // mark team as one that have visited this object
|
||||
VISITOR_GLOBAL, // mark player as one that have visited object of this type
|
||||
VISITOR_REMOVE, // unmark visitor, reversed to ADD
|
||||
VISITOR_CLEAR // clear all visitors from this object (object reset)
|
||||
VISITOR_ADD_HERO, // mark hero as one that have visited this object
|
||||
VISITOR_ADD_PLAYER, // mark player as one that have visited this object instance
|
||||
VISITOR_GLOBAL, // mark player as one that have visited object of this type
|
||||
VISITOR_SCOUTED, // marks targeted team as having scouted this object
|
||||
VISITOR_CLEAR, // clear all visitors from this object (object reset)
|
||||
};
|
||||
VisitMode mode = VISITOR_CLEAR; // uses VisitMode enum
|
||||
ObjectInstanceID object;
|
||||
|
@ -103,6 +103,7 @@ void Rewardable::Configuration::serializeJson(JsonSerializeFormat & handler)
|
||||
handler.serializeStruct("resetParameters", resetParameters);
|
||||
handler.serializeBool("canRefuse", canRefuse);
|
||||
handler.serializeBool("showScoutedPreview", showScoutedPreview);
|
||||
handler.serializeBool("coastVisitable", coastVisitable);
|
||||
handler.serializeInt("infoWindowType", infoWindowType);
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,8 @@ enum class EEventType
|
||||
EVENT_INVALID = 0,
|
||||
EVENT_FIRST_VISIT,
|
||||
EVENT_ALREADY_VISITED,
|
||||
EVENT_NOT_AVAILABLE
|
||||
EVENT_NOT_AVAILABLE,
|
||||
EVENT_GUARDED
|
||||
};
|
||||
|
||||
constexpr std::array<std::string_view, 4> SelectModeString{"selectFirst", "selectPlayer", "selectRandom", "selectAll"};
|
||||
@ -155,12 +156,16 @@ struct DLL_LINKAGE Configuration
|
||||
/// Limiter that will be used to determine that object is visited. Only if visit mode is set to "limiter"
|
||||
Rewardable::Limiter visitLimiter;
|
||||
|
||||
std::string guardsLayout;
|
||||
|
||||
/// if true - player can refuse visiting an object (e.g. Tomb)
|
||||
bool canRefuse = false;
|
||||
|
||||
/// if true - right-clicking object will show preview of object rewards
|
||||
bool showScoutedPreview = false;
|
||||
|
||||
bool coastVisitable = false;
|
||||
|
||||
/// if true - object info will shown in infobox (like resource pickup)
|
||||
EInfoWindowMode infoWindowType = EInfoWindowMode::AUTO;
|
||||
|
||||
@ -189,6 +194,13 @@ struct DLL_LINKAGE Configuration
|
||||
h & canRefuse;
|
||||
h & showScoutedPreview;
|
||||
h & infoWindowType;
|
||||
if (h.version >= Handler::Version::REWARDABLE_BANKS)
|
||||
{
|
||||
h & coastVisitable;
|
||||
h & guardsLayout;
|
||||
}
|
||||
else
|
||||
coastVisitable = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -174,6 +174,8 @@ void Rewardable::Info::configureReward(Rewardable::Configuration & object, vstd:
|
||||
reward.removeObject = source["removeObject"].Bool();
|
||||
reward.bonuses = randomizer.loadBonuses(source["bonuses"]);
|
||||
|
||||
reward.guards = randomizer.loadCreatures(source["guards"], rng, variables);
|
||||
|
||||
reward.primary = randomizer.loadPrimaries(source["primary"], rng, variables);
|
||||
reward.secondary = randomizer.loadSecondaries(source["secondary"], rng, variables);
|
||||
|
||||
@ -264,16 +266,64 @@ void Rewardable::Info::replaceTextPlaceholders(MetaString & target, const Variab
|
||||
|
||||
void Rewardable::Info::replaceTextPlaceholders(MetaString & target, const Variables & variables, const VisitInfo & info) const
|
||||
{
|
||||
for (const auto & artifact : info.reward.artifacts )
|
||||
target.replaceName(artifact);
|
||||
if (!info.reward.guards.empty())
|
||||
{
|
||||
replaceTextPlaceholders(target, variables);
|
||||
|
||||
for (const auto & spell : info.reward.spells )
|
||||
target.replaceName(spell);
|
||||
CreatureID strongest = info.reward.guards.at(0).getId();
|
||||
|
||||
for (const auto & secondary : info.reward.secondary )
|
||||
target.replaceName(secondary.first);
|
||||
for (const auto & guard : info.reward.guards )
|
||||
{
|
||||
if (strongest.toEntity(VLC)->getFightValue() < guard.getId().toEntity(VLC)->getFightValue())
|
||||
strongest = guard.getId();
|
||||
}
|
||||
target.replaceNamePlural(strongest); // FIXME: use singular if only 1 such unit is in guards
|
||||
|
||||
replaceTextPlaceholders(target, variables);
|
||||
MetaString loot;
|
||||
|
||||
for (GameResID it : GameResID::ALL_RESOURCES())
|
||||
{
|
||||
if (info.reward.resources[it] != 0)
|
||||
{
|
||||
loot.appendRawString("%d %s");
|
||||
loot.replaceNumber(info.reward.resources[it]);
|
||||
loot.replaceName(it);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto & artifact : info.reward.artifacts )
|
||||
{
|
||||
loot.appendRawString("%s");
|
||||
loot.replaceName(artifact);
|
||||
}
|
||||
|
||||
for (const auto & spell : info.reward.spells )
|
||||
{
|
||||
loot.appendRawString("%s");
|
||||
loot.replaceName(spell);
|
||||
}
|
||||
|
||||
for (const auto & secondary : info.reward.secondary )
|
||||
{
|
||||
loot.appendRawString("%s");
|
||||
loot.replaceName(secondary.first);
|
||||
}
|
||||
|
||||
target.replaceRawString(loot.buildList());
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto & artifact : info.reward.artifacts )
|
||||
target.replaceName(artifact);
|
||||
|
||||
for (const auto & spell : info.reward.spells )
|
||||
target.replaceName(spell);
|
||||
|
||||
for (const auto & secondary : info.reward.secondary )
|
||||
target.replaceName(secondary.first);
|
||||
|
||||
replaceTextPlaceholders(target, variables);
|
||||
}
|
||||
}
|
||||
|
||||
void Rewardable::Info::configureRewards(
|
||||
@ -378,10 +428,22 @@ void Rewardable::Info::configureObject(Rewardable::Configuration & object, vstd:
|
||||
object.info.push_back(onEmpty);
|
||||
}
|
||||
|
||||
if (!parameters["onGuardedMessage"].isNull())
|
||||
{
|
||||
Rewardable::VisitInfo onGuarded;
|
||||
onGuarded.visitType = Rewardable::EEventType::EVENT_GUARDED;
|
||||
onGuarded.message = loadMessage(parameters["onGuardedMessage"], TextIdentifier(objectTextID, "onGuarded"));
|
||||
replaceTextPlaceholders(onGuarded.message, object.variables);
|
||||
|
||||
object.info.push_back(onGuarded);
|
||||
}
|
||||
|
||||
configureResetInfo(object, rng, object.resetParameters, parameters["resetParameters"]);
|
||||
|
||||
object.canRefuse = parameters["canRefuse"].Bool();
|
||||
object.showScoutedPreview = parameters["showScoutedPreview"].Bool();
|
||||
object.guardsLayout = parameters["guardsLayout"].String();
|
||||
object.coastVisitable = parameters["coastVisitable"].Bool();
|
||||
|
||||
if(parameters["showInInfobox"].isNull())
|
||||
object.infoWindowType = EInfoWindowMode::AUTO;
|
||||
|
@ -82,6 +82,9 @@ struct DLL_LINKAGE Reward final
|
||||
/// fixed value, in form of percentage from max
|
||||
si32 movePercentage;
|
||||
|
||||
/// Guards that must be defeated in order to access this reward, empty if not guarded
|
||||
std::vector<CStackBasicDescriptor> guards;
|
||||
|
||||
/// list of bonuses, e.g. morale/luck
|
||||
std::vector<Bonus> bonuses;
|
||||
|
||||
|
@ -58,6 +58,8 @@ enum class ESerializationVersion : int32_t
|
||||
SAVE_COMPATIBILITY_FIXES, // 859 - implementation of previoulsy postponed changes to serialization
|
||||
CHRONICLES_SUPPORT, // 860 - support for heroes chronicles
|
||||
PER_MAP_GAME_SETTINGS, // 861 - game settings are now stored per-map
|
||||
CAMPAIGN_OUTRO_SUPPORT, // 862 - support for campaign outro video
|
||||
REWARDABLE_BANKS, // 863 - team state contains list of scouted objects, coast visitable rewardable objects
|
||||
|
||||
CURRENT = PER_MAP_GAME_SETTINGS
|
||||
CURRENT = REWARDABLE_BANKS
|
||||
};
|
||||
|
@ -67,22 +67,22 @@
|
||||
<message>
|
||||
<location filename="../mapsettings/generalsettings.ui" line="52"/>
|
||||
<source>Author</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Auteur</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mapsettings/generalsettings.ui" line="62"/>
|
||||
<source>Author contact (e.g. email)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Contact de l'auteur (e.g. email)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mapsettings/generalsettings.ui" line="72"/>
|
||||
<source>Map Creation Time</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Temps de Création de la carte</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mapsettings/generalsettings.ui" line="86"/>
|
||||
<source>Map Version</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Version de la carte</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mapsettings/generalsettings.ui" line="120"/>
|
||||
@ -149,37 +149,37 @@
|
||||
<message>
|
||||
<location filename="../inspector/herospellwidget.ui" line="29"/>
|
||||
<source>Spells</source>
|
||||
<translation type="unfinished">Sorts</translation>
|
||||
<translation>Sorts</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/herospellwidget.ui" line="47"/>
|
||||
<source>Customize spells</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Personnaliser les sorts</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/herospellwidget.ui" line="76"/>
|
||||
<source>Level 1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Niveau 1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/herospellwidget.ui" line="114"/>
|
||||
<source>Level 2</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Niveau 2</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/herospellwidget.ui" line="152"/>
|
||||
<source>Level 3</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Niveau 3</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/herospellwidget.ui" line="190"/>
|
||||
<source>Level 4</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Niveau 4</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/herospellwidget.ui" line="228"/>
|
||||
<source>Level 5</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Niveau 5</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -581,12 +581,12 @@
|
||||
<message>
|
||||
<location filename="../mainwindow.cpp" line="296"/>
|
||||
<source>Unsaved changes will be lost, are you sure?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Les modifications non sauvegardées seront perdues. Êtes-vous sûr ?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mainwindow.cpp" line="422"/>
|
||||
<source>Open map</source>
|
||||
<translation>Ouvrir la carte</translation>
|
||||
<translation>Ouvrir une carte</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mainwindow.cpp" line="424"/>
|
||||
@ -947,17 +947,17 @@
|
||||
<message>
|
||||
<location filename="../mapcontroller.cpp" line="405"/>
|
||||
<source>Can't place object</source>
|
||||
<translation type="unfinished">Impossible de placer l'objet</translation>
|
||||
<translation>Impossible de placer l'objet</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mapcontroller.cpp" line="577"/>
|
||||
<source>There can only be one grail object on the map.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Il ne peut y avoir qu'un objet Graal sur la carte.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mapcontroller.cpp" line="583"/>
|
||||
<source>Hero %1 cannot be created as NEUTRAL.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Le héro %1 ne peut pas être créé en tant que NEUTRE.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -1473,37 +1473,37 @@
|
||||
<message>
|
||||
<location filename="../inspector/townbuildingswidget.ui" line="53"/>
|
||||
<source>Build all</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Construire tout</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/townbuildingswidget.ui" line="60"/>
|
||||
<source>Demolish all</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Détruire tout</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/townbuildingswidget.ui" line="67"/>
|
||||
<source>Enable all</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Autoriser tout</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/townbuildingswidget.ui" line="74"/>
|
||||
<source>Disable all</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Interdire tout</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/townbuildingswidget.cpp" line="132"/>
|
||||
<source>Type</source>
|
||||
<translation type="unfinished">Type</translation>
|
||||
<translation>Type</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/townbuildingswidget.cpp" line="132"/>
|
||||
<source>Enabled</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Autorisé·e</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/townbuildingswidget.cpp" line="132"/>
|
||||
<source>Built</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Construit·e</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -1511,77 +1511,77 @@
|
||||
<message>
|
||||
<location filename="../inspector/towneventdialog.ui" line="23"/>
|
||||
<source>Town event</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Évènement de ville</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/towneventdialog.ui" line="42"/>
|
||||
<source>General</source>
|
||||
<translation type="unfinished">Général</translation>
|
||||
<translation>Général</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/towneventdialog.ui" line="57"/>
|
||||
<source>Event name</source>
|
||||
<translation type="unfinished">Nom de l'évènement</translation>
|
||||
<translation>Nom de l'évènement</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/towneventdialog.ui" line="64"/>
|
||||
<source>Type event message text</source>
|
||||
<translation type="unfinished">Taper le message d'évènement</translation>
|
||||
<translation>Taper le message d'évènement</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/towneventdialog.ui" line="85"/>
|
||||
<source>Day of first occurrence</source>
|
||||
<translation type="unfinished">Jour de la première occurrence</translation>
|
||||
<translation>Jour de la première occurrence</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/towneventdialog.ui" line="99"/>
|
||||
<source>Repeat after (0 = no repeat)</source>
|
||||
<translation type="unfinished">Récurrence (0 = pas de récurrence)</translation>
|
||||
<translation>Réoéter après (0 = pas de répétition)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/towneventdialog.ui" line="123"/>
|
||||
<source>Affected players</source>
|
||||
<translation type="unfinished">Joueurs affectés</translation>
|
||||
<translation>Joueurs affectés</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/towneventdialog.ui" line="146"/>
|
||||
<source>affects human</source>
|
||||
<translation type="unfinished">afttecte les joueurs</translation>
|
||||
<translation>afttecte les joueurs</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/towneventdialog.ui" line="155"/>
|
||||
<source>affects AI</source>
|
||||
<translation type="unfinished">affecte l'ordinateur</translation>
|
||||
<translation>affecte l'ordinateur</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/towneventdialog.ui" line="166"/>
|
||||
<source>Resources</source>
|
||||
<translation type="unfinished">Resources</translation>
|
||||
<translation>Ressources</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/towneventdialog.ui" line="198"/>
|
||||
<source>Buildings</source>
|
||||
<translation type="unfinished">Bâtiments</translation>
|
||||
<translation>Bâtiments</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/towneventdialog.ui" line="219"/>
|
||||
<source>Creatures</source>
|
||||
<translation type="unfinished">Créatures</translation>
|
||||
<translation>Créatures</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/towneventdialog.ui" line="258"/>
|
||||
<source>OK</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>OK</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/towneventdialog.cpp" line="163"/>
|
||||
<source>Creature level %1 / Creature level %1 Upgrade</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Créature niveau %1 / Créature niveau %1 Augmenté</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/towneventdialog.cpp" line="205"/>
|
||||
<source>Day %1 - %2</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Jour %1 - %2</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -1589,32 +1589,32 @@
|
||||
<message>
|
||||
<location filename="../inspector/towneventswidget.ui" line="29"/>
|
||||
<source>Town events</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Évènements de ville</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/towneventswidget.ui" line="37"/>
|
||||
<source>Timed events</source>
|
||||
<translation type="unfinished">Evenements timés</translation>
|
||||
<translation>Évènements temporels</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/towneventswidget.ui" line="63"/>
|
||||
<source>Add</source>
|
||||
<translation type="unfinished">Ajouter</translation>
|
||||
<translation>Ajouter</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/towneventswidget.ui" line="76"/>
|
||||
<source>Remove</source>
|
||||
<translation type="unfinished">Supprimer</translation>
|
||||
<translation>Supprimer</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/towneventswidget.cpp" line="106"/>
|
||||
<source>Day %1 - %2</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Jour %1 - %2</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/towneventswidget.cpp" line="127"/>
|
||||
<source>New event</source>
|
||||
<translation type="unfinished">Nouvel évènement</translation>
|
||||
<translation>Nouvel évènement</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -1622,17 +1622,17 @@
|
||||
<message>
|
||||
<location filename="../inspector/townspellswidget.ui" line="29"/>
|
||||
<source>Spells</source>
|
||||
<translation type="unfinished">Sorts</translation>
|
||||
<translation>Sorts</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/townspellswidget.ui" line="47"/>
|
||||
<source>Customize spells</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Personnaliser les sorts</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/townspellswidget.ui" line="76"/>
|
||||
<source>Level 1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Niveau 1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/townspellswidget.ui" line="93"/>
|
||||
@ -1641,7 +1641,7 @@
|
||||
<location filename="../inspector/townspellswidget.ui" line="231"/>
|
||||
<location filename="../inspector/townspellswidget.ui" line="277"/>
|
||||
<source>Spell that may appear in mage guild</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Sort qui peut apparaitre dans la Guilde des Mages</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/townspellswidget.ui" line="100"/>
|
||||
@ -1649,28 +1649,28 @@
|
||||
<location filename="../inspector/townspellswidget.ui" line="192"/>
|
||||
<location filename="../inspector/townspellswidget.ui" line="238"/>
|
||||
<location filename="../inspector/townspellswidget.ui" line="284"/>
|
||||
<source>Spell that must appear in mage guild</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<source>Sort qui doit apparaitre dans la Guilde des Mages</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/townspellswidget.ui" line="122"/>
|
||||
<source>Level 2</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Niveau 2</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/townspellswidget.ui" line="168"/>
|
||||
<source>Level 3</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Niveau 3</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/townspellswidget.ui" line="214"/>
|
||||
<source>Level 4</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Niveau 4</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../inspector/townspellswidget.ui" line="260"/>
|
||||
<source>Level 5</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Niveau 5</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -1762,17 +1762,17 @@
|
||||
<message>
|
||||
<location filename="../validator.cpp" line="148"/>
|
||||
<source>Spell scroll % 1 doesn't have instance assigned and must be removed</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Le défilement de sort %1 n'a pas d'instance assignée et doit être enlevé</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../validator.cpp" line="154"/>
|
||||
<source>Artifact % 1 is prohibited by map settings</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>L'artéfact %1 est interdit par la configuration de la carte</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../validator.cpp" line="168"/>
|
||||
<source>Player %1 has no towns and heroes assigned</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Le joueur %1 n'a pas de ville ni de héro assigné</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../validator.cpp" line="116"/>
|
||||
@ -1963,32 +1963,32 @@
|
||||
<message>
|
||||
<location filename="../windownewmap.ui" line="164"/>
|
||||
<source>S (36x36)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>S (36x36)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../windownewmap.ui" line="169"/>
|
||||
<source>M (72x72)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>M (72x72)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../windownewmap.ui" line="174"/>
|
||||
<source>L (108x108)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>L (108x108)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../windownewmap.ui" line="184"/>
|
||||
<source>H (180x180)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>H (180x180)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../windownewmap.ui" line="189"/>
|
||||
<source>XH (216x216)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>XH (216x216)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../windownewmap.ui" line="194"/>
|
||||
<source>G (252x252)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>G (252x252)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../windownewmap.ui" line="248"/>
|
||||
|
@ -4114,17 +4114,12 @@ void CGameHandler::newObject(CGObjectInstance * object, PlayerColor initiator)
|
||||
sendAndApply(&no);
|
||||
}
|
||||
|
||||
void CGameHandler::startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank, const CGTownInstance *town)
|
||||
void CGameHandler::startBattle(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const BattleLayout & layout, const CGTownInstance *town)
|
||||
{
|
||||
battles->startBattlePrimary(army1, army2, tile, hero1, hero2, creatureBank, town);
|
||||
battles->startBattle(army1, army2, tile, hero1, hero2, layout, town);
|
||||
}
|
||||
|
||||
void CGameHandler::startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank )
|
||||
void CGameHandler::startBattle(const CArmedInstance *army1, const CArmedInstance *army2 )
|
||||
{
|
||||
battles->startBattleI(army1, army2, tile, creatureBank);
|
||||
}
|
||||
|
||||
void CGameHandler::startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank )
|
||||
{
|
||||
battles->startBattleI(army1, army2, creatureBank);
|
||||
battles->startBattle(army1, army2);
|
||||
}
|
||||
|
@ -149,9 +149,8 @@ public:
|
||||
|
||||
void heroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) override;
|
||||
void stopHeroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) override;
|
||||
void startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank = false, const CGTownInstance *town = nullptr) override; //use hero=nullptr for no hero
|
||||
void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank = false) override; //if any of armies is hero, hero will be used
|
||||
void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false) override; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle
|
||||
void startBattle(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const BattleLayout & layout, const CGTownInstance *town) override; //use hero=nullptr for no hero
|
||||
void startBattle(const CArmedInstance *army1, const CArmedInstance *army2) override; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle
|
||||
bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode movementMode, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL) override;
|
||||
void giveHeroBonus(GiveBonus * bonus) override;
|
||||
void setMovePoints(SetMovePoints * smp) override;
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "../../lib/battle/CBattleInfoCallback.h"
|
||||
#include "../../lib/battle/CObstacleInstance.h"
|
||||
#include "../../lib/battle/BattleInfo.h"
|
||||
#include "../../lib/battle/BattleLayout.h"
|
||||
#include "../../lib/entities/building/TownFortifications.h"
|
||||
#include "../../lib/gameState/CGameState.h"
|
||||
#include "../../lib/mapping/CMap.h"
|
||||
@ -52,9 +53,8 @@ void BattleProcessor::engageIntoBattle(PlayerColor player)
|
||||
gameHandler->sendAndApply(&pb);
|
||||
}
|
||||
|
||||
void BattleProcessor::restartBattlePrimary(const BattleID & battleID, const CArmedInstance *army1, const CArmedInstance *army2, int3 tile,
|
||||
const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank,
|
||||
const CGTownInstance *town)
|
||||
void BattleProcessor::restartBattle(const BattleID & battleID, const CArmedInstance *army1, const CArmedInstance *army2, int3 tile,
|
||||
const CGHeroInstance *hero1, const CGHeroInstance *hero2, const BattleLayout & layout, const CGTownInstance *town)
|
||||
{
|
||||
auto battle = gameHandler->gameState()->getBattle(battleID);
|
||||
|
||||
@ -90,12 +90,11 @@ void BattleProcessor::restartBattlePrimary(const BattleID & battleID, const CArm
|
||||
bc.battleID = battleID;
|
||||
gameHandler->sendAndApply(&bc);
|
||||
|
||||
startBattlePrimary(army1, army2, tile, hero1, hero2, creatureBank, town);
|
||||
startBattle(army1, army2, tile, hero1, hero2, layout, town);
|
||||
}
|
||||
|
||||
void BattleProcessor::startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile,
|
||||
const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank,
|
||||
const CGTownInstance *town)
|
||||
void BattleProcessor::startBattle(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile,
|
||||
const CGHeroInstance *hero1, const CGHeroInstance *hero2, const BattleLayout & layout, const CGTownInstance *town)
|
||||
{
|
||||
assert(gameHandler->gameState()->getBattle(army1->getOwner()) == nullptr);
|
||||
assert(gameHandler->gameState()->getBattle(army2->getOwner()) == nullptr);
|
||||
@ -103,7 +102,7 @@ void BattleProcessor::startBattlePrimary(const CArmedInstance *army1, const CArm
|
||||
BattleSideArray<const CArmedInstance *> armies{army1, army2};
|
||||
BattleSideArray<const CGHeroInstance*>heroes{hero1, hero2};
|
||||
|
||||
auto battleID = setupBattle(tile, armies, heroes, creatureBank, town); //initializes stacks, places creatures on battlefield, blocks and informs player interfaces
|
||||
auto battleID = setupBattle(tile, armies, heroes, layout, town); //initializes stacks, places creatures on battlefield, blocks and informs player interfaces
|
||||
|
||||
const auto * battle = gameHandler->gameState()->getBattle(battleID);
|
||||
assert(battle);
|
||||
@ -144,20 +143,16 @@ void BattleProcessor::startBattlePrimary(const CArmedInstance *army1, const CArm
|
||||
flowProcessor->onBattleStarted(*battle);
|
||||
}
|
||||
|
||||
void BattleProcessor::startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank)
|
||||
void BattleProcessor::startBattle(const CArmedInstance *army1, const CArmedInstance *army2)
|
||||
{
|
||||
startBattlePrimary(army1, army2, tile,
|
||||
army1->ID == Obj::HERO ? static_cast<const CGHeroInstance*>(army1) : nullptr,
|
||||
army2->ID == Obj::HERO ? static_cast<const CGHeroInstance*>(army2) : nullptr,
|
||||
creatureBank);
|
||||
startBattle(army1, army2, army2->visitablePos(),
|
||||
army1->ID == Obj::HERO ? dynamic_cast<const CGHeroInstance*>(army1) : nullptr,
|
||||
army2->ID == Obj::HERO ? dynamic_cast<const CGHeroInstance*>(army2) : nullptr,
|
||||
BattleLayout::createDefaultLayout(gameHandler, army1, army2),
|
||||
nullptr);
|
||||
}
|
||||
|
||||
void BattleProcessor::startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank)
|
||||
{
|
||||
startBattleI(army1, army2, army2->visitablePos(), creatureBank);
|
||||
}
|
||||
|
||||
BattleID BattleProcessor::setupBattle(int3 tile, BattleSideArray<const CArmedInstance *> armies, BattleSideArray<const CGHeroInstance *> heroes, bool creatureBank, const CGTownInstance *town)
|
||||
BattleID BattleProcessor::setupBattle(int3 tile, BattleSideArray<const CArmedInstance *> armies, BattleSideArray<const CGHeroInstance *> heroes, const BattleLayout & layout, const CGTownInstance *town)
|
||||
{
|
||||
const auto & t = *gameHandler->getTile(tile);
|
||||
TerrainId terrain = t.terType->getId();
|
||||
@ -170,7 +165,7 @@ BattleID BattleProcessor::setupBattle(int3 tile, BattleSideArray<const CArmedIns
|
||||
|
||||
//send info about battles
|
||||
BattleStart bs;
|
||||
bs.info = BattleInfo::setupBattle(tile, terrain, terType, armies, heroes, creatureBank, town);
|
||||
bs.info = BattleInfo::setupBattle(tile, terrain, terType, armies, heroes, layout, town);
|
||||
bs.battleID = gameHandler->gameState()->nextBattleID;
|
||||
|
||||
engageIntoBattle(bs.info->getSide(BattleSide::ATTACKER).color);
|
||||
|
@ -20,6 +20,7 @@ class BattleAction;
|
||||
class int3;
|
||||
class CBattleInfoCallback;
|
||||
struct BattleResult;
|
||||
struct BattleLayout;
|
||||
class BattleID;
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
||||
@ -45,7 +46,7 @@ class BattleProcessor : boost::noncopyable
|
||||
void engageIntoBattle(PlayerColor player);
|
||||
|
||||
bool checkBattleStateChanges(const CBattleInfoCallback & battle);
|
||||
BattleID setupBattle(int3 tile, BattleSideArray<const CArmedInstance *> armies, BattleSideArray<const CGHeroInstance *> heroes, bool creatureBank, const CGTownInstance *town);
|
||||
BattleID setupBattle(int3 tile, BattleSideArray<const CArmedInstance *> armies, BattleSideArray<const CGHeroInstance *> heroes, const BattleLayout & layout, const CGTownInstance *town);
|
||||
|
||||
bool makeAutomaticBattleAction(const CBattleInfoCallback & battle, const BattleAction & ba);
|
||||
|
||||
@ -56,13 +57,11 @@ public:
|
||||
~BattleProcessor();
|
||||
|
||||
/// Starts battle with specified parameters
|
||||
void startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank = false, const CGTownInstance *town = nullptr);
|
||||
/// Starts battle between two armies (which can also be heroes) at specified tile
|
||||
void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank = false);
|
||||
void startBattle(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const BattleLayout & layout, const CGTownInstance *town);
|
||||
/// Starts battle between two armies (which can also be heroes) at position of 2nd object
|
||||
void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false);
|
||||
void startBattle(const CArmedInstance *army1, const CArmedInstance *army2);
|
||||
/// Restart ongoing battle and end previous battle
|
||||
void restartBattlePrimary(const BattleID & battleID, const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank = false, const CGTownInstance *town = nullptr);
|
||||
void restartBattle(const BattleID & battleID, const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const BattleLayout & layout, const CGTownInstance *town);
|
||||
|
||||
/// Processing of incoming battle action netpack
|
||||
bool makePlayerBattleAction(const BattleID & battleID, PlayerColor player, const BattleAction & ba);
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
#include "../../lib/battle/IBattleState.h"
|
||||
#include "../../lib/battle/SideInBattle.h"
|
||||
#include "../../lib/battle/BattleLayout.h"
|
||||
#include "../../lib/CPlayerState.h"
|
||||
#include "../../lib/mapObjects/CGObjectInstance.h"
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
@ -95,14 +96,14 @@ void CBattleDialogQuery::onRemoval(PlayerColor color)
|
||||
assert(answer);
|
||||
if(*answer == 1)
|
||||
{
|
||||
gh->battles->restartBattlePrimary(
|
||||
gh->battles->restartBattle(
|
||||
bi->getBattleID(),
|
||||
bi->getSideArmy(BattleSide::ATTACKER),
|
||||
bi->getSideArmy(BattleSide::DEFENDER),
|
||||
bi->getLocation(),
|
||||
bi->getSideHero(BattleSide::ATTACKER),
|
||||
bi->getSideHero(BattleSide::DEFENDER),
|
||||
bi->isCreatureBank(),
|
||||
bi->getLayout(),
|
||||
bi->getDefendedTown()
|
||||
);
|
||||
}
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "../../lib/TerrainHandler.h"
|
||||
|
||||
#include "../../lib/battle/BattleInfo.h"
|
||||
#include "../../lib/battle/BattleLayout.h"
|
||||
#include "../../lib/CStack.h"
|
||||
|
||||
#include "../../lib/filesystem/ResourcePath.h"
|
||||
@ -197,10 +198,11 @@ public:
|
||||
|
||||
auto terrain = t.terType->getId();
|
||||
BattleField terType(0);
|
||||
BattleLayout layout = BattleLayout::createDefaultLayout(gameState->callback, attacker, defender);
|
||||
|
||||
//send info about battles
|
||||
|
||||
BattleInfo * battle = BattleInfo::setupBattle(tile, terrain, terType, armedInstancies, heroes, false, nullptr);
|
||||
BattleInfo * battle = BattleInfo::setupBattle(tile, terrain, terType, armedInstancies, heroes, layout, nullptr);
|
||||
|
||||
BattleStart bs;
|
||||
bs.info = battle;
|
||||
|
@ -79,9 +79,8 @@ public:
|
||||
void heroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) override {}
|
||||
void stopHeroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) override {}
|
||||
void visitCastleObjects(const CGTownInstance * obj, const CGHeroInstance * hero) override {}
|
||||
void startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank = false, const CGTownInstance *town = nullptr) override {} //use hero=nullptr for no hero
|
||||
void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank = false) override {} //if any of armies is hero, hero will be used
|
||||
void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false) override {} //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle
|
||||
void startBattle(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const BattleLayout & layout, const CGTownInstance *town) override {} //use hero=nullptr for no hero
|
||||
void startBattle(const CArmedInstance *army1, const CArmedInstance *army2) override {}
|
||||
bool moveHero(ObjectInstanceID hid, int3 dst, EMovementMode movementMode, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL) override {return false;}
|
||||
bool swapGarrisonOnSiege(ObjectInstanceID tid) override {return false;}
|
||||
void giveHeroBonus(GiveBonus * bonus) override {}
|
||||
|
@ -11,6 +11,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "../../lib/battle/IBattleState.h"
|
||||
#include "../../lib/battle/BattleLayout.h"
|
||||
#include "../../lib/int3.h"
|
||||
|
||||
class BattleStateMock : public IBattleState
|
||||
@ -37,7 +38,7 @@ public:
|
||||
MOCK_CONST_METHOD3(getActualDamage, int64_t(const DamageRange &, int32_t, vstd::RNG &));
|
||||
MOCK_CONST_METHOD0(getBattleID, BattleID());
|
||||
MOCK_CONST_METHOD0(getLocation, int3());
|
||||
MOCK_CONST_METHOD0(isCreatureBank, bool());
|
||||
MOCK_CONST_METHOD0(getLayout, BattleLayout());
|
||||
MOCK_CONST_METHOD1(getUsedSpells, std::vector<SpellID>(BattleSide));
|
||||
|
||||
MOCK_METHOD0(nextRound, void());
|
||||
|
Loading…
x
Reference in New Issue
Block a user