1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-02-03 13:01:33 +02:00

Merge 'develop' branch

This commit is contained in:
Dydzio 2023-01-11 16:24:53 +01:00
commit aac515f64e
169 changed files with 8437 additions and 3989 deletions

View File

@ -80,7 +80,7 @@ public:
//void actionFinished(const BattleAction &action) override;//occurs AFTER every action taken by any stack or by the hero
//void actionStarted(const BattleAction &action) override;//occurs BEFORE every action taken by any stack or by the hero
//void battleAttack(const BattleAttack *ba) override; //called when stack is performing attack
//void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa) override; //called when stack receives damage (after battleAttack())
//void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, bool ranged) override; //called when stack receives damage (after battleAttack())
//void battleEnd(const BattleResult *br) override;
//void battleResultsApplied() override; //called when all effects of last battle are applied
//void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied;

View File

@ -31,7 +31,7 @@ engineBase::engineBase()
void engineBase::configure()
{
engine.configure("Minimum", "Maximum", "Minimum", "AlgebraicSum", "Centroid", "Proportional");
logAi->info(engine.toString());
logAi->trace(engine.toString());
}
void engineBase::addRule(const std::string & txt)

View File

@ -177,7 +177,7 @@ void CStupidAI::battleAttack(const BattleAttack *ba)
print("battleAttack called");
}
void CStupidAI::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa)
void CStupidAI::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, bool ranged)
{
print("battleStacksAttacked called");
}
@ -202,7 +202,7 @@ void CStupidAI::battleNewRound(int round)
print("battleNewRound called");
}
void CStupidAI::battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance)
void CStupidAI::battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport)
{
print("battleStackMoved called");
}

View File

@ -31,12 +31,12 @@ public:
BattleAction activeStack(const CStack * stack) override; //called when it's turn of that stack
void battleAttack(const BattleAttack *ba) override; //called when stack is performing attack
void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa) override; //called when stack receives damage (after battleAttack())
void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, bool ranged) override; //called when stack receives damage (after battleAttack())
void battleEnd(const BattleResult *br) override;
//void battleResultsApplied() override; //called when all effects of last battle are applied
void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied;
void battleNewRound(int round) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
void battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance) override;
void battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport) override;
void battleSpellCast(const BattleSpellCast *sc) override;
void battleStacksEffectsSet(const SetStackEffect & sse) override;//called when a specific effect is set to stacks
//void battleTriggerEffect(const BattleTriggerEffect & bte) override;

View File

@ -30,7 +30,7 @@ engineBase::engineBase()
void engineBase::configure()
{
engine.configure("Minimum", "Maximum", "Minimum", "AlgebraicSum", "Centroid", "Proportional");
logAi->info(engine.toString());
logAi->trace(engine.toString());
}
void engineBase::addRule(const std::string & txt)

View File

@ -8,4 +8,4 @@ sudo apt-get install libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf
sudo apt-get install qtbase5-dev
sudo apt-get install ninja-build zlib1g-dev libavformat-dev libswscale-dev libtbb-dev libluajit-5.1-dev
# Optional dependencies
sudo apt-get install libminizip-dev libfuzzylite-dev
sudo apt-get install libminizip-dev libfuzzylite-dev qttools5-dev

View File

@ -1,5 +1,14 @@
#!/bin/sh
# steps to upgrade MXE dependencies:
# 1) Use Debian/Ubuntu system or install one (virtual machines will work too)
# 2) update following script to include any new dependencies
# You can also run it to upgrade existing ones, but don't expect much
# MXE repository only provides ancient versions for the sake of "stability"
# https://github.com/vcmi/vcmi-deps-mxe/blob/master/mirror-mxe.sh
# 3) make release in vcmi-deps-mxe repository using resulting tar archive
# 4) update paths to tar archive in this script
# Install nsis for installer creation
sudo add-apt-repository 'deb http://security.ubuntu.com/ubuntu bionic-security main'
sudo apt-get install -qq nsis ninja-build libssl1.0.0

View File

@ -48,7 +48,9 @@ option(ENABLE_ERM "Enable compilation of ERM scripting module" OFF)
option(ENABLE_LUA "Enable compilation of LUA scripting module" OFF)
option(ENABLE_LAUNCHER "Enable compilation of launcher" ON)
option(ENABLE_EDITOR "Enable compilation of map editor" ON)
option(ENABLE_TRANSLATIONS "Enable generation of translations for launcher and editor" ON)
option(ENABLE_NULLKILLER_AI "Enable compilation of Nullkiller AI library" ON)
if(APPLE_IOS)
set(BUNDLE_IDENTIFIER_PREFIX "" CACHE STRING "Bundle identifier prefix")
set(APP_DISPLAY_NAME "VCMI" CACHE STRING "App name on the home screen")
@ -127,14 +129,22 @@ else()
endif(ENABLE_GITVERSION)
# Precompiled header configuration
if(ENABLE_PCH AND NOT ${CMAKE_VERSION} VERSION_LESS "3.16.0")
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0 )
set(ENABLE_PCH OFF) # broken
endif()
if( ${CMAKE_VERSION} VERSION_LESS "3.16.0")
set(ENABLE_PCH OFF) #not supported
endif()
if(ENABLE_PCH)
macro(enable_pch name)
target_precompile_headers(${name} PRIVATE $<$<COMPILE_LANGUAGE:CXX>:<StdInc.h$<ANGLE-R>>)
endmacro(enable_pch)
else(ENABLE_PCH AND NOT ${CMAKE_VERSION} VERSION_LESS "3.16.0")
else()
macro(enable_pch ignore)
endmacro(enable_pch)
endif(ENABLE_PCH AND NOT ${CMAKE_VERSION} VERSION_LESS "3.16.0")
endif()
############################################
# Documentation section #
@ -349,6 +359,15 @@ if(ENABLE_LAUNCHER OR ENABLE_EDITOR)
# Widgets finds its own dependencies (QtGui and QtCore).
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network)
find_package(QT NAMES Qt6 Qt5 COMPONENTS LinguistTools)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS LinguistTools)
if(NOT Qt${QT_VERSION_MAJOR}LinguistTools_DIR)
set(ENABLE_TRANSLATIONS OFF)
endif()
if(ENABLE_TRANSLATIONS)
add_definitions(-DENABLE_QT_TRANSLATIONS)
endif()
endif()
if(ENABLE_NULLKILLER_AI)

View File

@ -718,6 +718,13 @@ namespace vstd
return v;
}
//c++20 feature
template<typename Arithmetic, typename Floating>
Arithmetic lerp(const Arithmetic & a, const Arithmetic & b, const Floating & f)
{
return a + (b - a) * f;
}
using boost::math::round;
}
using vstd::operator-=;

View File

@ -20,7 +20,7 @@ namespace BitmapHandler
{
SDL_Surface * loadH3PCX(ui8 * data, size_t size);
SDL_Surface * loadBitmapFromDir(std::string path, std::string fname, bool setKey=true);
SDL_Surface * loadBitmapFromDir(std::string path, std::string fname);
}
bool isPCX(const ui8 *header)//check whether file can be PCX according to header
@ -102,7 +102,7 @@ SDL_Surface * BitmapHandler::loadH3PCX(ui8 * pcx, size_t size)
return ret;
}
SDL_Surface * BitmapHandler::loadBitmapFromDir(std::string path, std::string fname, bool setKey)
SDL_Surface * BitmapHandler::loadBitmapFromDir(std::string path, std::string fname)
{
if(!fname.size())
{
@ -121,14 +121,7 @@ SDL_Surface * BitmapHandler::loadBitmapFromDir(std::string path, std::string fna
if (isPCX(readFile.first.get()))
{//H3-style PCX
ret = loadH3PCX(readFile.first.get(), readFile.second);
if (ret)
{
if(ret->format->BytesPerPixel == 1 && setKey)
{
CSDL_Ext::setColorKey(ret,ret->format->palette->colors[0]);
}
}
else
if (!ret)
{
logGlobal->error("Failed to open %s as H3 PCX!", fname);
return nullptr;
@ -144,7 +137,8 @@ SDL_Surface * BitmapHandler::loadBitmapFromDir(std::string path, std::string fna
{
if (ret->format->palette)
{
//set correct value for alpha\unused channel
// set correct value for alpha\unused channel
// NOTE: might be unnecessary with SDL2
for (int i=0; i < ret->format->palette->ncolors; i++)
ret->format->palette->colors[i].a = SDL_ALPHA_OPAQUE;
}
@ -196,12 +190,12 @@ SDL_Surface * BitmapHandler::loadBitmapFromDir(std::string path, std::string fna
return ret;
}
SDL_Surface * BitmapHandler::loadBitmap(std::string fname, bool setKey)
SDL_Surface * BitmapHandler::loadBitmap(std::string fname)
{
SDL_Surface * bitmap = nullptr;
if (!(bitmap = loadBitmapFromDir("DATA/", fname, setKey)) &&
!(bitmap = loadBitmapFromDir("SPRITES/", fname, setKey)))
if (!(bitmap = loadBitmapFromDir("DATA/", fname)) &&
!(bitmap = loadBitmapFromDir("SPRITES/", fname)))
{
logGlobal->error("Error: Failed to find file %s", fname);
}

View File

@ -14,5 +14,5 @@ struct SDL_Surface;
namespace BitmapHandler
{
//Load file from /DATA or /SPRITES
SDL_Surface * loadBitmap(std::string fname, bool setKey=true);
SDL_Surface * loadBitmap(std::string fname);
}

View File

@ -460,10 +460,9 @@ int main(int argc, char * argv[])
if(!settings["session"]["headless"].Bool())
{
pomtime.getDiff();
CCS->curh = new CCursorHandler();
graphics = new Graphics(); // should be before curh->init()
graphics = new Graphics(); // should be before curh
CCS->curh->initCursor();
CCS->curh = new CCursorHandler();
logGlobal->info("Screen handler: %d ms", pomtime.getDiff());
pomtime.getDiff();
@ -1040,80 +1039,89 @@ static void mainLoop()
}
}
static void quitApplication()
{
if(!settings["session"]["headless"].Bool())
{
if(CSH->client)
CSH->endGameplay();
}
GH.listInt.clear();
GH.objsToBlit.clear();
CMM.reset();
if(!settings["session"]["headless"].Bool())
{
// cleanup, mostly to remove false leaks from analyzer
if(CCS)
{
CCS->musich->release();
CCS->soundh->release();
vstd::clear_pointer(CCS);
}
CMessage::dispose();
vstd::clear_pointer(graphics);
}
vstd::clear_pointer(VLC);
vstd::clear_pointer(console);// should be removed after everything else since used by logging
boost::this_thread::sleep(boost::posix_time::milliseconds(750));//???
if(!settings["session"]["headless"].Bool())
{
if(settings["general"]["notifications"].Bool())
{
NotificationHandler::destroy();
}
cleanupRenderer();
if(nullptr != mainRenderer)
{
SDL_DestroyRenderer(mainRenderer);
mainRenderer = nullptr;
}
if(nullptr != mainWindow)
{
SDL_DestroyWindow(mainWindow);
mainWindow = nullptr;
}
SDL_Quit();
}
if(logConfig != nullptr)
{
logConfig->deconfigure();
delete logConfig;
logConfig = nullptr;
}
std::cout << "Ending...\n";
exit(0);
}
void handleQuit(bool ask)
{
auto quitApplication = []()
{
if(!settings["session"]["headless"].Bool())
{
if(CSH->client)
CSH->endGameplay();
}
GH.listInt.clear();
GH.objsToBlit.clear();
CMM.reset();
if(!settings["session"]["headless"].Bool())
{
// cleanup, mostly to remove false leaks from analyzer
if(CCS)
{
CCS->musich->release();
CCS->soundh->release();
vstd::clear_pointer(CCS);
}
CMessage::dispose();
vstd::clear_pointer(graphics);
}
vstd::clear_pointer(VLC);
vstd::clear_pointer(console);// should be removed after everything else since used by logging
boost::this_thread::sleep(boost::posix_time::milliseconds(750));//???
if(!settings["session"]["headless"].Bool())
{
if(settings["general"]["notifications"].Bool())
{
NotificationHandler::destroy();
}
cleanupRenderer();
if(nullptr != mainRenderer)
{
SDL_DestroyRenderer(mainRenderer);
mainRenderer = nullptr;
}
if(nullptr != mainWindow)
{
SDL_DestroyWindow(mainWindow);
mainWindow = nullptr;
}
SDL_Quit();
}
if(logConfig != nullptr)
{
logConfig->deconfigure();
delete logConfig;
logConfig = nullptr;
}
std::cout << "Ending...\n";
exit(0);
};
if(CSH->client && LOCPLINT && ask)
{
CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[69], quitApplication, nullptr);
CCS->curh->set(Cursor::Map::POINTER);
LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[69], [](){
// Workaround for assertion failure on exit:
// handleQuit() is alway called during SDL event processing
// during which, eventsM is kept locked
// this leads to assertion failure if boost::mutex is in locked state
eventsM.unlock();
quitApplication();
}, nullptr);
}
else
{

View File

@ -4,7 +4,6 @@ set(client_SRCS
battle/BattleActionsController.cpp
battle/BattleAnimationClasses.cpp
battle/BattleControlPanel.cpp
battle/BattleEffectsController.cpp
battle/BattleFieldController.cpp
battle/BattleInterfaceClasses.cpp
@ -14,6 +13,7 @@ set(client_SRCS
battle/BattleRenderer.cpp
battle/BattleSiegeController.cpp
battle/BattleStacksController.cpp
battle/BattleWindow.cpp
battle/CreatureAnimation.cpp
gui/CAnimation.cpp
@ -21,6 +21,7 @@ set(client_SRCS
gui/CCursorHandler.cpp
gui/CGuiHandler.cpp
gui/CIntObject.cpp
gui/ColorFilter.cpp
gui/Fonts.cpp
gui/Geometries.cpp
gui/SDL_Extensions.cpp
@ -89,7 +90,6 @@ set(client_HEADERS
battle/BattleActionsController.h
battle/BattleAnimationClasses.h
battle/BattleControlPanel.h
battle/BattleEffectsController.h
battle/BattleFieldController.h
battle/BattleInterfaceClasses.h
@ -99,12 +99,15 @@ set(client_HEADERS
battle/BattleRenderer.h
battle/BattleSiegeController.h
battle/BattleStacksController.h
battle/BattleWindow.h
battle/CreatureAnimation.h
battle/BattleConstants.h
gui/CAnimation.h
gui/Canvas.h
gui/CCursorHandler.h
gui/CGuiHandler.h
gui/ColorFilter.h
gui/CIntObject.h
gui/Fonts.h
gui/Geometries.h

View File

@ -16,7 +16,7 @@
#include "battle/BattleEffectsController.h"
#include "battle/BattleFieldController.h"
#include "battle/BattleInterfaceClasses.h"
#include "battle/BattleControlPanel.h"
#include "battle/BattleWindow.h"
#include "../CCallback.h"
#include "windows/CCastleInterface.h"
#include "gui/CCursorHandler.h"
@ -93,7 +93,7 @@ boost::recursive_mutex * CPlayerInterface::pim = new boost::recursive_mutex;
CPlayerInterface * LOCPLINT;
BattleInterface * CPlayerInterface::battleInt;
std::shared_ptr<BattleInterface> CPlayerInterface::battleInt;
enum EMoveState {STOP_MOVE, WAITING_MOVE, CONTINUE_MOVE, DURING_MOVE};
CondSh<EMoveState> stillMoveHero(STOP_MOVE); //used during hero movement
@ -142,7 +142,9 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player)
CPlayerInterface::~CPlayerInterface()
{
if(CCS->soundh) CCS->soundh->ambientStopAllChannels();
if(CCS && CCS->soundh)
CCS->soundh->ambientStopAllChannels();
logGlobal->trace("\tHuman player interface for player %s being destructed", playerID.getStr());
delete showingDialog;
delete cingconsole;
@ -707,7 +709,7 @@ void CPlayerInterface::battleStart(const CCreatureSet *army1, const CCreatureSet
BATTLE_EVENT_POSSIBLE_RETURN;
}
void CPlayerInterface::battleUnitsChanged(const std::vector<UnitChanges> & units, const std::vector<CustomEffectInfo> & customEffects)
void CPlayerInterface::battleUnitsChanged(const std::vector<UnitChanges> & units)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
BATTLE_EVENT_POSSIBLE_RETURN;
@ -747,8 +749,6 @@ void CPlayerInterface::battleUnitsChanged(const std::vector<UnitChanges> & units
break;
}
}
battleInt->effectsController->displayCustomEffects(customEffects);
}
void CPlayerInterface::battleObstaclesChanged(const std::vector<ObstacleChanges> & obstacles)
@ -838,9 +838,9 @@ BattleAction CPlayerInterface::activeStack(const CStack * stack) //called when i
autofightingAI.reset();
}
BattleInterface *b = battleInt;
assert(battleInt);
if(!b)
if(!battleInt)
{
return BattleAction::makeDefend(stack); // probably battle is finished already
}
@ -853,7 +853,7 @@ BattleAction CPlayerInterface::activeStack(const CStack * stack) //called when i
{
boost::unique_lock<boost::recursive_mutex> un(*pim);
b->stackActivated(stack);
battleInt->stackActivated(stack);
//Regeneration & mana drain go there
}
//wait till BattleInterface sets its command
@ -915,12 +915,12 @@ void CPlayerInterface::battleLogMessage(const std::vector<MetaString> & lines)
battleInt->displayBattleLog(lines);
}
void CPlayerInterface::battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance)
void CPlayerInterface::battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
BATTLE_EVENT_POSSIBLE_RETURN;
battleInt->stackMoved(stack, dest, distance);
battleInt->stackMoved(stack, dest, distance, teleport);
}
void CPlayerInterface::battleSpellCast( const BattleSpellCast *sc )
{
@ -944,7 +944,7 @@ void CPlayerInterface::battleTriggerEffect (const BattleTriggerEffect & bte)
RETURN_IF_QUICK_COMBAT;
battleInt->effectsController->battleTriggerEffect(bte);
}
void CPlayerInterface::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa)
void CPlayerInterface::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, bool ranged)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
BATTLE_EVENT_POSSIBLE_RETURN;
@ -954,24 +954,25 @@ void CPlayerInterface::battleStacksAttacked(const std::vector<BattleStackAttacke
{
const CStack * defender = cb->battleGetStackByID(elem.stackAttacked, false);
const CStack * attacker = cb->battleGetStackByID(elem.attackerID, false);
if(elem.isEffect())
{
if(defender && !elem.isSecondary())
battleInt->effectsController->displayEffect(EBattleEffect::EBattleEffect(elem.effect), defender->getPosition());
}
if(elem.isSpell())
{
if(defender)
battleInt->displaySpellEffect(elem.spellID, defender->getPosition());
}
//FIXME: why action is deleted during enchanter cast?
bool remoteAttack = false;
if(LOCPLINT->curAction)
remoteAttack |= LOCPLINT->curAction->actionType != EActionType::WALK_AND_ATTACK;
assert(defender);
StackAttackedInfo to_put = {defender, elem.damageAmount, elem.killedAmount, attacker, remoteAttack, elem.killed(), elem.willRebirth(), elem.cloneKilled()};
arg.push_back(to_put);
StackAttackedInfo info;
info.defender = defender;
info.attacker = attacker;
info.damageDealt = elem.damageAmount;
info.amountKilled = elem.killedAmount;
info.spellEffect = SpellID::NONE;
info.indirectAttack = ranged;
info.killed = elem.killed();
info.rebirth = elem.willRebirth();
info.cloneKilled = elem.cloneKilled();
info.fireShield = elem.fireShield();
if (elem.isSpell())
info.spellEffect = elem.spellID;
arg.push_back(info);
}
battleInt->stacksAreAttacked(arg);
}
@ -982,94 +983,36 @@ void CPlayerInterface::battleAttack(const BattleAttack * ba)
assert(curAction);
const CStack * attacker = cb->battleGetStackByID(ba->stackAttacking);
StackAttackInfo info;
info.attacker = cb->battleGetStackByID(ba->stackAttacking);
info.defender = nullptr;
info.indirectAttack = ba->shot();
info.lucky = ba->lucky();
info.unlucky = ba->unlucky();
info.deathBlow = ba->deathBlow();
info.lifeDrain = ba->lifeDrain();
info.tile = ba->tile;
info.spellEffect = SpellID::NONE;
if(!attacker)
{
logGlobal->error("Attacking stack not found");
return;
}
if (ba->spellLike())
info.spellEffect = ba->spellID;
if(ba->lucky()) //lucky hit
for(auto & elem : ba->bsa)
{
battleInt->controlPanel->console->addText(attacker->formatGeneralMessage(-45));
battleInt->effectsController->displayEffect(EBattleEffect::GOOD_LUCK, soundBase::GOODLUCK, attacker->getPosition());
}
if(ba->unlucky()) //unlucky hit
{
battleInt->controlPanel->console->addText(attacker->formatGeneralMessage(-44));
battleInt->effectsController->displayEffect(EBattleEffect::BAD_LUCK, soundBase::BADLUCK, attacker->getPosition());
}
if(ba->deathBlow())
{
battleInt->controlPanel->console->addText(attacker->formatGeneralMessage(365));
for(auto & elem : ba->bsa)
if(!elem.isSecondary())
{
const CStack * attacked = cb->battleGetStackByID(elem.stackAttacked);
battleInt->effectsController->displayEffect(EBattleEffect::DEATH_BLOW, attacked->getPosition());
assert(info.defender == nullptr);
info.defender = cb->battleGetStackByID(elem.stackAttacked);
}
CCS->soundh->playSound(soundBase::deathBlow);
}
battleInt->effectsController->displayCustomEffects(ba->customEffects);
battleInt->waitForAnims();
auto actionTarget = curAction->getTarget(cb.get());
if(actionTarget.empty() || (actionTarget.size() < 2 && !ba->shot()))
{
logNetwork->error("Invalid current action: no destination.");
return;
}
if(ba->shot())
{
for(auto & elem : ba->bsa)
else
{
if(!elem.isSecondary()) //display projectile only for primary target
{
const CStack * attacked = cb->battleGetStackByID(elem.stackAttacked);
battleInt->stackAttacking(attacker, attacked->getPosition(), attacked, true);
}
info.secondaryDefender.push_back(cb->battleGetStackByID(elem.stackAttacked));
}
}
else
{
auto attackTarget = actionTarget.at(1).hexValue;
assert(info.defender != nullptr);
assert(info.attacker != nullptr);
//TODO: use information from BattleAttack but not curAction
int shift = 0;
if(ba->counter() && BattleHex::mutualPosition(attackTarget, attacker->getPosition()) < 0)
{
int distp = BattleHex::getDistance(attackTarget + 1, attacker->getPosition());
int distm = BattleHex::getDistance(attackTarget - 1, attacker->getPosition());
if(distp < distm)
shift = 1;
else
shift = -1;
}
if(!ba->bsa.empty())
{
const CStack * attacked = cb->battleGetStackByID(ba->bsa.begin()->stackAttacked);
battleInt->stackAttacking(attacker, ba->counter() ? BattleHex(attackTarget + shift) : attackTarget, attacked, false);
}
}
//battleInt->waitForAnims(); //FIXME: freeze
if(ba->spellLike())
{
//TODO: use information from BattleAttack but not curAction
auto destination = actionTarget.at(0).hexValue;
//display hit animation
SpellID spellID = ba->spellID;
battleInt->displaySpellHit(spellID, destination);
}
battleInt->stackAttacking(info);
}
void CPlayerInterface::battleGateStateChanged(const EGateState state)
@ -1603,7 +1546,7 @@ void CPlayerInterface::playerBlocked(int reason, bool start)
GH.curInt = this;
adventureInt->selection = nullptr;
adventureInt->setPlayer(playerID);
std::string msg = CGI->generaltexth->localizedTexts["adventureMap"]["playerAttacked"].String();
std::string msg = CGI->generaltexth->translate("vcmi.adventureMap.playerAttacked");
boost::replace_first(msg, "%s", cb->getStartInfo()->playerInfos.find(playerID)->second.name);
std::vector<std::shared_ptr<CComponent>> cmp;
cmp.push_back(std::make_shared<CComponent>(CComponent::flag, playerID.getNum(), 0));

View File

@ -84,7 +84,7 @@ public:
static const int SAVES_COUNT = 5;
CCastleInterface * castleInt; //nullptr if castle window isn't opened
static BattleInterface * battleInt; //nullptr if no battle
static std::shared_ptr<BattleInterface> battleInt; //nullptr if no battle
CInGameConsole * cingconsole;
std::shared_ptr<CCallback> cb; //to communicate with engine
@ -193,14 +193,14 @@ public:
void battleNewRoundFirst(int round) override; //called at the beginning of each turn before changes are applied; used for HP regen handling
void battleNewRound(int round) override; //called at the beginning of each turn, round=-1 is the tactic phase, round=0 is the first "normal" turn
void battleLogMessage(const std::vector<MetaString> & lines) override;
void battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance) override;
void battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport) override;
void battleSpellCast(const BattleSpellCast *sc) override;
void battleStacksEffectsSet(const SetStackEffect & sse) override; //called when a specific effect is set to stacks
void battleTriggerEffect(const BattleTriggerEffect & bte) override; //various one-shot effect
void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa) override;
void battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, bool ranged) override;
void battleStartBefore(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2) override; //called by engine just before battle starts; side=0 - left, side=1 - right
void battleStart(const CCreatureSet *army1, const CCreatureSet *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool side) override; //called by engine when battle starts; side=0 - left, side=1 - right
void battleUnitsChanged(const std::vector<UnitChanges> & units, const std::vector<CustomEffectInfo> & customEffects) override;
void battleUnitsChanged(const std::vector<UnitChanges> & units) override;
void battleObstaclesChanged(const std::vector<ObstacleChanges> & obstacles) override;
void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack
void battleGateStateChanged(const EGateState state) override;

View File

@ -176,7 +176,7 @@ void CServerHandler::startLocalServerAndConnect()
th->update();
auto errorMsg = CGI->generaltexth->localizedTexts["server"]["errors"]["existingProcess"].String();
auto errorMsg = CGI->generaltexth->translate("vcmi.server.errors.existingProcess");
try
{
CConnection testConnection(localhostAddress, getDefaultPort(), NAME, uuid);
@ -718,7 +718,7 @@ void CServerHandler::restoreLastSession()
saveSession->Bool() = false;
};
CInfoWindow::showYesNoDialog(VLC->generaltexth->localizedTexts["server"]["confirmReconnect"].String(), {}, loadSession, cleanUpSession);
CInfoWindow::showYesNoDialog(VLC->generaltexth->translate("vcmi.server.confirmReconnect"), {}, loadSession, cleanUpSession);
}
void CServerHandler::debugStartTest(std::string filename, bool save)

View File

@ -380,20 +380,12 @@ void CClient::endGame()
{
boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
logNetwork->info("Ending current game!");
if(GH.topInt())
{
GH.topInt()->deactivate();
}
GH.listInt.clear();
GH.objsToBlit.clear();
GH.statusbar = nullptr;
logNetwork->info("Removed GUI.");
removeGUI();
vstd::clear_pointer(const_cast<CGameInfo *>(CGI)->mh);
vstd::clear_pointer(gs);
logNetwork->info("Deleted mapHandler and gameState.");
LOCPLINT = nullptr;
}
playerint.clear();
@ -594,11 +586,10 @@ void CClient::battleStarted(const BattleInfo * info)
if(!settings["session"]["headless"].Bool())
{
Rect battleIntRect((screen->w - 800)/2, (screen->h - 600)/2, 800, 600);
if(!!att || !!def)
{
boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
GH.pushIntT<BattleInterface>(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero, battleIntRect, att, def);
CPlayerInterface::battleInt = std::make_shared<BattleInterface>(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero, att, def);
}
else if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool())
{
@ -606,7 +597,7 @@ void CClient::battleStarted(const BattleInfo * info)
auto spectratorInt = std::dynamic_pointer_cast<CPlayerInterface>(playerint[PlayerColor::SPECTATOR]);
spectratorInt->cb->setBattle(info);
boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
GH.pushIntT<BattleInterface>(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero, battleIntRect, att, def, spectratorInt);
CPlayerInterface::battleInt = std::make_shared<BattleInterface>(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero, att, def, spectratorInt);
}
}

View File

@ -18,7 +18,7 @@ CreatureCostBox::CreatureCostBox(Rect position, std::string titleText)
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
type |= REDRAW_PARENT;
pos = position + pos;
pos = position + pos.topLeft();
title = std::make_shared<CLabel>(pos.w/2, 10, FONT_SMALL, ETextAlignment::CENTER, Colors::WHITE, titleText);
}

View File

@ -741,7 +741,7 @@ void BattleResult::applyFirstCl(CClient *cl)
void BattleStackMoved::applyFirstCl(CClient *cl)
{
const CStack * movedStack = GS(cl)->curB->battleGetStackByID(stack);
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStackMoved, movedStack, tilesToMove, distance);
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStackMoved, movedStack, tilesToMove, distance, teleporting);
}
void BattleAttack::applyFirstCl(CClient *cl)
@ -751,7 +751,7 @@ void BattleAttack::applyFirstCl(CClient *cl)
void BattleAttack::applyCl(CClient *cl)
{
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, bsa);
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, bsa, shot());
}
void StartAction::applyFirstCl(CClient *cl)
@ -773,7 +773,7 @@ void SetStackEffect::applyCl(CClient *cl)
void StacksInjured::applyCl(CClient *cl)
{
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, stacks);
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleStacksAttacked, stacks, false);
}
void BattleResultsApplied::applyCl(CClient *cl)
@ -785,7 +785,7 @@ void BattleResultsApplied::applyCl(CClient *cl)
void BattleUnitsChanged::applyCl(CClient * cl)
{
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleUnitsChanged, changedStacks, customEffects);
callBattleInterfaceIfPresentForBothSides(cl, &IBattleEventsReceiver::battleUnitsChanged, changedStacks);
}
void BattleObstaclesChanged::applyCl(CClient *cl)

View File

@ -10,7 +10,7 @@
#include "StdInc.h"
#include "BattleActionsController.h"
#include "BattleControlPanel.h"
#include "BattleWindow.h"
#include "BattleStacksController.h"
#include "BattleInterface.h"
#include "BattleFieldController.h"
@ -60,7 +60,7 @@ void BattleActionsController::endCastingSpell()
currentSpell = nullptr;
spellDestSelectMode = false;
CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
CCS->curh->set(Cursor::Combat::POINTER);
if(owner.stacksController->getActiveStack())
{
@ -122,7 +122,7 @@ void BattleActionsController::enterCreatureCastingMode()
owner.giveCommand(EActionType::MONSTER_SPELL, BattleHex::INVALID, owner.stacksController->activeStackSpellToCast());
owner.stacksController->setSelectedStack(nullptr);
CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
CCS->curh->set(Cursor::Combat::POINTER);
}
}
else
@ -171,8 +171,6 @@ void BattleActionsController::reorderPossibleActionsPriority(const CStack * stac
break;
case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
return 2; break;
case PossiblePlayerBattleAction::RISE_DEMONS:
return 3; break;
case PossiblePlayerBattleAction::SHOOT:
return 4; break;
case PossiblePlayerBattleAction::ATTACK_AND_RETURN:
@ -233,7 +231,7 @@ void BattleActionsController::castThisSpell(SpellID spellID)
void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
{
if (!owner.myTurn || !owner.battleActionsStarted) //we are not permit to do anything
if (!owner.myTurn) //we are not permit to do anything
return;
// This function handles mouse move over hexes and l-clicking on them.
@ -245,8 +243,8 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
std::string newConsoleMsg;
//used when hovering -> tooltip message and cursor to be set
bool setCursor = true; //if we want to suppress setting cursor
ECursor::ECursorTypes cursorType = ECursor::COMBAT;
int cursorFrame = ECursor::COMBAT_POINTER; //TODO: is this line used?
bool spellcastingCursor = false;
auto cursorFrame = Cursor::Combat::POINTER;
//used when l-clicking -> action to be called upon the click
std::function<void()> realizeAction;
@ -263,9 +261,6 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
if (shere)
ourStack = shere->owner == owner.curInt->playerID;
//stack may have changed, update selection border
owner.stacksController->setHoveredStack(shere);
localActions.clear();
illegalActions.clear();
@ -301,7 +296,6 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
{
if (owner.fieldController->isTileAttackable(myNumber)) // move isTileAttackable to be part of battleCanAttack?
{
owner.fieldController->setBattleCursor(myNumber); // temporary - needed for following function :(
BattleHex attackFromHex = owner.fieldController->fromWhichHexAttack(myNumber);
if (attackFromHex >= 0) //we can be in this line when unreachable creature is L - clicked (as of revision 1308)
@ -378,19 +372,6 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
if (shere && ourStack && shere->canBeHealed())
legalAction = true;
break;
case PossiblePlayerBattleAction::RISE_DEMONS:
if (shere && ourStack && !shere->alive())
{
if (!(shere->hasBonusOfType(Bonus::UNDEAD)
|| shere->hasBonusOfType(Bonus::NON_LIVING)
|| shere->hasBonusOfType(Bonus::GARGOYLE)
|| shere->summoned
|| shere->isClone()
|| shere->hasBonusOfType(Bonus::SIEGE_WEAPON)
))
legalAction = true;
}
break;
}
if (legalAction)
localActions.push_back (action);
@ -434,12 +415,12 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
case PossiblePlayerBattleAction::MOVE_STACK:
if (owner.stacksController->getActiveStack()->hasBonusOfType(Bonus::FLYING))
{
cursorFrame = ECursor::COMBAT_FLY;
cursorFrame = Cursor::Combat::FLY;
newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[295]) % owner.stacksController->getActiveStack()->getName()).str(); //Fly %s here
}
else
{
cursorFrame = ECursor::COMBAT_MOVE;
cursorFrame = Cursor::Combat::MOVE;
newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[294]) % owner.stacksController->getActiveStack()->getName()).str(); //Move %s here
}
@ -464,7 +445,7 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
case PossiblePlayerBattleAction::WALK_AND_ATTACK:
case PossiblePlayerBattleAction::ATTACK_AND_RETURN: //TODO: allow to disable return
{
owner.fieldController->setBattleCursor(myNumber); //handle direction of cursor and attackable tile
owner.fieldController->setBattleCursor(myNumber); //handle direction of cursor
setCursor = false; //don't overwrite settings from the call above //TODO: what does it mean?
bool returnAfterAttack = currentAction == PossiblePlayerBattleAction::ATTACK_AND_RETURN;
@ -487,9 +468,9 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
case PossiblePlayerBattleAction::SHOOT:
{
if (owner.curInt->cb->battleHasShootingPenalty(owner.stacksController->getActiveStack(), myNumber))
cursorFrame = ECursor::COMBAT_SHOOT_PENALTY;
cursorFrame = Cursor::Combat::SHOOT_PENALTY;
else
cursorFrame = ECursor::COMBAT_SHOOT;
cursorFrame = Cursor::Combat::SHOOT;
realizeAction = [=](){owner.giveCommand(EActionType::SHOOT, myNumber);};
TDmgRange damage = owner.curInt->cb->battleEstimateDamage(owner.stacksController->getActiveStack(), shere);
@ -524,7 +505,7 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
break;
case PossiblePlayerBattleAction::TELEPORT:
newConsoleMsg = CGI->generaltexth->allTexts[25]; //Teleport Here
cursorFrame = ECursor::COMBAT_TELEPORT;
cursorFrame = Cursor::Combat::TELEPORT;
isCastingPossible = true;
break;
case PossiblePlayerBattleAction::OBSTACLE:
@ -534,7 +515,7 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
break;
case PossiblePlayerBattleAction::SACRIFICE:
newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[549]) % shere->getName()).str(); //sacrifice the %s
cursorFrame = ECursor::COMBAT_SACRIFICE;
cursorFrame = Cursor::Combat::SACRIFICE;
isCastingPossible = true;
break;
case PossiblePlayerBattleAction::FREE_LOCATION:
@ -542,24 +523,17 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
isCastingPossible = true;
break;
case PossiblePlayerBattleAction::HEAL:
cursorFrame = ECursor::COMBAT_HEAL;
cursorFrame = Cursor::Combat::HEAL;
newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[419]) % shere->getName()).str(); //Apply first aid to the %s
realizeAction = [=](){ owner.giveCommand(EActionType::STACK_HEAL, myNumber); }; //command healing
break;
case PossiblePlayerBattleAction::RISE_DEMONS:
cursorType = ECursor::SPELLBOOK;
realizeAction = [=]()
{
owner.giveCommand(EActionType::DAEMON_SUMMONING, myNumber);
};
break;
case PossiblePlayerBattleAction::CATAPULT:
cursorFrame = ECursor::COMBAT_SHOOT_CATAPULT;
cursorFrame = Cursor::Combat::SHOOT_CATAPULT;
realizeAction = [=](){ owner.giveCommand(EActionType::CATAPULT, myNumber); };
break;
case PossiblePlayerBattleAction::CREATURE_INFO:
{
cursorFrame = ECursor::COMBAT_QUERY;
cursorFrame = Cursor::Combat::QUERY;
newConsoleMsg = (boost::format(CGI->generaltexth->allTexts[297]) % shere->getName()).str();
realizeAction = [=](){ GH.pushIntT<CStackWindow>(shere, false); };
break;
@ -572,25 +546,25 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
{
case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE:
case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL:
cursorFrame = ECursor::COMBAT_BLOCKED;
cursorFrame = Cursor::Combat::BLOCKED;
newConsoleMsg = CGI->generaltexth->allTexts[23];
break;
case PossiblePlayerBattleAction::TELEPORT:
cursorFrame = ECursor::COMBAT_BLOCKED;
cursorFrame = Cursor::Combat::BLOCKED;
newConsoleMsg = CGI->generaltexth->allTexts[24]; //Invalid Teleport Destination
break;
case PossiblePlayerBattleAction::SACRIFICE:
newConsoleMsg = CGI->generaltexth->allTexts[543]; //choose army to sacrifice
break;
case PossiblePlayerBattleAction::FREE_LOCATION:
cursorFrame = ECursor::COMBAT_BLOCKED;
cursorFrame = Cursor::Combat::BLOCKED;
newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[181]) % currentSpell->name); //No room to place %s here
break;
default:
if (myNumber == -1)
CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER); //set neutral cursor over menu etc.
CCS->curh->set(Cursor::Combat::POINTER);
else
cursorFrame = ECursor::COMBAT_BLOCKED;
cursorFrame = Cursor::Combat::BLOCKED;
break;
}
}
@ -603,8 +577,7 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
case PossiblePlayerBattleAction::SACRIFICE:
break;
default:
cursorType = ECursor::SPELLBOOK;
cursorFrame = 0;
spellcastingCursor = true;
if (newConsoleMsg.empty() && currentSpell)
newConsoleMsg = boost::str(boost::format(CGI->generaltexth->allTexts[26]) % currentSpell->name); //Cast %s
break;
@ -665,12 +638,17 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
if (eventType == CIntObject::MOVE)
{
if (setCursor)
CCS->curh->changeGraphic(cursorType, cursorFrame);
{
if (spellcastingCursor)
CCS->curh->set(Cursor::Spellcast::SPELL);
else
CCS->curh->set(cursorFrame);
}
if (!currentConsoleMsg.empty())
owner.controlPanel->console->clearIfMatching(currentConsoleMsg);
GH.statusbar->clearIfMatching(currentConsoleMsg);
if (!newConsoleMsg.empty())
owner.controlPanel->console->write(newConsoleMsg);
GH.statusbar->write(newConsoleMsg);
currentConsoleMsg = newConsoleMsg;
}
@ -683,8 +661,8 @@ void BattleActionsController::handleHex(BattleHex myNumber, int eventType)
}
realizeAction();
if (!secondaryTarget) //do not replace teleport or sacrifice cursor
CCS->curh->changeGraphic(ECursor::COMBAT, ECursor::COMBAT_POINTER);
owner.controlPanel->console->clear();
CCS->curh->set(Cursor::Combat::POINTER);
GH.statusbar->clear();
}
}
}
@ -781,7 +759,7 @@ void BattleActionsController::activateStack()
break;
}
}
owner.controlPanel->setAlternativeActions(actionsToSelect);
owner.windowObject->setAlternativeActions(actionsToSelect);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -10,36 +10,39 @@
#pragma once
#include "../../lib/battle/BattleHex.h"
#include "../../lib/CSoundBase.h"
#include "../widgets/Images.h"
#include "../gui/Geometries.h"
#include "BattleConstants.h"
VCMI_LIB_NAMESPACE_BEGIN
class CStack;
class CCreature;
class CSpell;
VCMI_LIB_NAMESPACE_END
struct SDL_Color;
class ColorFilter;
class BattleHero;
class CAnimation;
class BattleInterface;
class CreatureAnimation;
class CBattleAnimation;
struct CatapultProjectileInfo;
struct StackAttackedInfo;
struct Point;
/// Base class of battle animations
class CBattleAnimation
class BattleAnimation
{
protected:
BattleInterface & owner;
bool initialized;
std::vector<CBattleAnimation *> & pendingAnimations();
std::vector<BattleAnimation *> & pendingAnimations();
std::shared_ptr<CreatureAnimation> stackAnimation(const CStack * stack) const;
bool stackFacingRight(const CStack * stack);
void setStackFacingRight(const CStack * stack, bool facingRight);
virtual bool init() = 0; //to be called - if returned false, call again until returns true
bool checkInitialConditions(); //determines if this animation is earliest of all
public:
ui32 ID; //unique identifier
@ -47,107 +50,101 @@ public:
bool isInitialized();
bool tryInitialize();
virtual void nextFrame() {} //call every new frame
virtual ~CBattleAnimation();
virtual ~BattleAnimation();
CBattleAnimation(BattleInterface & owner);
BattleAnimation(BattleInterface & owner);
};
/// Sub-class which is responsible for managing the battle stack animation.
class CBattleStackAnimation : public CBattleAnimation
class BattleStackAnimation : public BattleAnimation
{
public:
std::shared_ptr<CreatureAnimation> myAnim; //animation for our stack, managed by CBattleInterface
std::shared_ptr<CreatureAnimation> myAnim; //animation for our stack, managed by BattleInterface
const CStack * stack; //id of stack whose animation it is
CBattleStackAnimation(BattleInterface & owner, const CStack * _stack);
void shiftColor(const ColorShifter * shifter);
BattleStackAnimation(BattleInterface & owner, const CStack * _stack);
void rotateStack(BattleHex hex);
};
/// This class is responsible for managing the battle attack animation
class CAttackAnimation : public CBattleStackAnimation
class StackActionAnimation : public BattleStackAnimation
{
bool soundPlayed;
protected:
BattleHex dest; //attacked hex
bool shooting;
CCreatureAnim::EAnimType group; //if shooting is true, print this animation group
const CStack *attackedStack;
const CStack *attackingStack;
int attackingStackPosBeforeReturn; //for stacks with return_after_strike feature
const CCreature * getCreature() const;
ECreatureAnimType nextGroup;
ECreatureAnimType currGroup;
std::string sound;
public:
void nextFrame() override;
bool checkInitialConditions();
void setNextGroup( ECreatureAnimType group );
void setGroup( ECreatureAnimType group );
void setSound( std::string sound );
CAttackAnimation(BattleInterface & owner, const CStack *attacker, BattleHex _dest, const CStack *defender);
~CAttackAnimation();
ECreatureAnimType getGroup() const;
StackActionAnimation(BattleInterface & owner, const CStack * _stack);
~StackActionAnimation();
bool init() override;
};
/// Animation of a defending unit
class CDefenceAnimation : public CBattleStackAnimation
class DefenceAnimation : public StackActionAnimation
{
CCreatureAnim::EAnimType getMyAnimType();
std::string getMySound();
void startAnimation();
const CStack * attacker; //attacking stack
bool rangedAttack; //if true, stack has been attacked by shooting
bool killed; //if true, stack has been killed
float timeToWait; // for how long this animation should be paused
public:
DefenceAnimation(BattleInterface & owner, const CStack * stack);
};
/// Animation of a hit unit
class HittedAnimation : public StackActionAnimation
{
public:
HittedAnimation(BattleInterface & owner, const CStack * stack);
};
/// Animation of a dying unit
class DeathAnimation : public StackActionAnimation
{
public:
DeathAnimation(BattleInterface & owner, const CStack * stack, bool ranged);
};
/// Resurrects stack from dead state
class ResurrectionAnimation : public StackActionAnimation
{
public:
ResurrectionAnimation(BattleInterface & owner, const CStack * _stack);
};
class ColorTransformAnimation : public BattleStackAnimation
{
std::vector<ColorFilter> steps;
std::vector<float> timePoints;
const CSpell * spell;
float totalProgress;
bool init() override;
void nextFrame() override;
CDefenceAnimation(StackAttackedInfo _attackedInfo, BattleInterface & owner);
~CDefenceAnimation();
};
class CDummyAnimation : public CBattleAnimation
{
private:
int counter;
int howMany;
public:
bool init() override;
void nextFrame() override;
CDummyAnimation(BattleInterface & owner, int howManyFrames);
};
/// Hand-to-hand attack
class CMeleeAttackAnimation : public CAttackAnimation
{
public:
bool init() override;
CMeleeAttackAnimation(BattleInterface & owner, const CStack * attacker, BattleHex _dest, const CStack * _attacked);
ColorTransformAnimation(BattleInterface & owner, const CStack * _stack, const std::string & colorFilterName, const CSpell * spell);
};
/// Base class for all animations that play during stack movement
class CStackMoveAnimation : public CBattleStackAnimation
class StackMoveAnimation : public BattleStackAnimation
{
public:
BattleHex currentHex;
BattleHex nextHex;
BattleHex prevHex;
protected:
CStackMoveAnimation(BattleInterface & owner, const CStack * _stack, BattleHex _currentHex);
StackMoveAnimation(BattleInterface & owner, const CStack * _stack, BattleHex prevHex, BattleHex nextHex);
};
/// Move animation of a creature
class CMovementAnimation : public CStackMoveAnimation
class MovementAnimation : public StackMoveAnimation
{
private:
std::vector<BattleHex> destTiles; //full path, includes already passed hexes
ui32 curentMoveIndex; // index of nextHex in destTiles
BattleHex oldPos; //position of stack before move
double begX, begY; // starting position
double distanceX, distanceY; // full movement distance, may be negative if creture moves topleft
@ -158,45 +155,73 @@ public:
bool init() override;
void nextFrame() override;
CMovementAnimation(BattleInterface & owner, const CStack *_stack, std::vector<BattleHex> _destTiles, int _distance);
~CMovementAnimation();
MovementAnimation(BattleInterface & owner, const CStack *_stack, std::vector<BattleHex> _destTiles, int _distance);
~MovementAnimation();
};
/// Move end animation of a creature
class CMovementEndAnimation : public CStackMoveAnimation
class MovementEndAnimation : public StackMoveAnimation
{
public:
bool init() override;
CMovementEndAnimation(BattleInterface & owner, const CStack * _stack, BattleHex destTile);
~CMovementEndAnimation();
MovementEndAnimation(BattleInterface & owner, const CStack * _stack, BattleHex destTile);
~MovementEndAnimation();
};
/// Move start animation of a creature
class CMovementStartAnimation : public CStackMoveAnimation
class MovementStartAnimation : public StackMoveAnimation
{
public:
bool init() override;
CMovementStartAnimation(BattleInterface & owner, const CStack * _stack);
MovementStartAnimation(BattleInterface & owner, const CStack * _stack);
};
/// Class responsible for animation of stack chaning direction (left <-> right)
class CReverseAnimation : public CStackMoveAnimation
class ReverseAnimation : public StackMoveAnimation
{
void setupSecondPart();
public:
bool priority; //true - high, false - low
bool init() override;
void setupSecondPart();
CReverseAnimation(BattleInterface & owner, const CStack * stack, BattleHex dest, bool _priority);
~CReverseAnimation();
ReverseAnimation(BattleInterface & owner, const CStack * stack, BattleHex dest);
};
class CRangedAttackAnimation : public CAttackAnimation
/// This class is responsible for managing the battle attack animation
class AttackAnimation : public StackActionAnimation
{
protected:
BattleHex dest; //attacked hex
const CStack *defendingStack;
const CStack *attackingStack;
int attackingStackPosBeforeReturn; //for stacks with return_after_strike feature
const CCreature * getCreature() const;
ECreatureAnimType findValidGroup( const std::vector<ECreatureAnimType> candidates ) const;
public:
AttackAnimation(BattleInterface & owner, const CStack *attacker, BattleHex _dest, const CStack *defender);
};
/// Hand-to-hand attack
class MeleeAttackAnimation : public AttackAnimation
{
ECreatureAnimType getUpwardsGroup(bool multiAttack) const;
ECreatureAnimType getForwardGroup(bool multiAttack) const;
ECreatureAnimType getDownwardsGroup(bool multiAttack) const;
ECreatureAnimType selectGroup(bool multiAttack);
public:
MeleeAttackAnimation(BattleInterface & owner, const CStack * attacker, BattleHex _dest, const CStack * _attacked, bool multiAttack);
void nextFrame() override;
};
class RangedAttackAnimation : public AttackAnimation
{
void setAnimationGroup();
void initializeProjectile();
void emitProjectile();
@ -205,85 +230,81 @@ class CRangedAttackAnimation : public CAttackAnimation
protected:
bool projectileEmitted;
virtual CCreatureAnim::EAnimType getUpwardsGroup() const = 0;
virtual CCreatureAnim::EAnimType getForwardGroup() const = 0;
virtual CCreatureAnim::EAnimType getDownwardsGroup() const = 0;
virtual ECreatureAnimType getUpwardsGroup() const = 0;
virtual ECreatureAnimType getForwardGroup() const = 0;
virtual ECreatureAnimType getDownwardsGroup() const = 0;
virtual void createProjectile(const Point & from, const Point & dest) const = 0;
virtual uint32_t getAttackClimaxFrame() const = 0;
public:
CRangedAttackAnimation(BattleInterface & owner, const CStack * attacker, BattleHex dest, const CStack * defender);
~CRangedAttackAnimation();
RangedAttackAnimation(BattleInterface & owner, const CStack * attacker, BattleHex dest, const CStack * defender);
~RangedAttackAnimation();
bool init() override;
void nextFrame() override;
};
/// Shooting attack
class CShootingAnimation : public CRangedAttackAnimation
class ShootingAnimation : public RangedAttackAnimation
{
CCreatureAnim::EAnimType getUpwardsGroup() const override;
CCreatureAnim::EAnimType getForwardGroup() const override;
CCreatureAnim::EAnimType getDownwardsGroup() const override;
ECreatureAnimType getUpwardsGroup() const override;
ECreatureAnimType getForwardGroup() const override;
ECreatureAnimType getDownwardsGroup() const override;
void createProjectile(const Point & from, const Point & dest) const override;
uint32_t getAttackClimaxFrame() const override;
public:
CShootingAnimation(BattleInterface & owner, const CStack * attacker, BattleHex dest, const CStack * defender);
ShootingAnimation(BattleInterface & owner, const CStack * attacker, BattleHex dest, const CStack * defender);
};
/// Catapult attack
class CCatapultAnimation : public CShootingAnimation
class CatapultAnimation : public ShootingAnimation
{
private:
bool explosionEmitted;
int catapultDamage;
public:
CCatapultAnimation(BattleInterface & owner, const CStack * attacker, BattleHex dest, const CStack * defender, int _catapultDmg = 0);
CatapultAnimation(BattleInterface & owner, const CStack * attacker, BattleHex dest, const CStack * defender, int _catapultDmg = 0);
void createProjectile(const Point & from, const Point & dest) const override;
void nextFrame() override;
};
class CCastAnimation : public CRangedAttackAnimation
class CastAnimation : public RangedAttackAnimation
{
const CSpell * spell;
CCreatureAnim::EAnimType findValidGroup( const std::vector<CCreatureAnim::EAnimType> candidates ) const;
CCreatureAnim::EAnimType getUpwardsGroup() const override;
CCreatureAnim::EAnimType getForwardGroup() const override;
CCreatureAnim::EAnimType getDownwardsGroup() const override;
ECreatureAnimType getUpwardsGroup() const override;
ECreatureAnimType getForwardGroup() const override;
ECreatureAnimType getDownwardsGroup() const override;
void createProjectile(const Point & from, const Point & dest) const override;
uint32_t getAttackClimaxFrame() const override;
public:
CCastAnimation(BattleInterface & owner, const CStack * attacker, BattleHex dest_, const CStack * defender, const CSpell * spell);
CastAnimation(BattleInterface & owner, const CStack * attacker, BattleHex dest_, const CStack * defender, const CSpell * spell);
};
struct CPointEffectParameters
class DummyAnimation : public BattleAnimation
{
std::vector<Point> positions;
std::vector<BattleHex> tiles;
std::string animation;
private:
int counter;
int howMany;
public:
bool init() override;
void nextFrame() override;
soundBase::soundID sound = soundBase::invalid;
BattleHex boundHex = BattleHex::INVALID;
bool aligntoBottom = false;
bool waitForSound = false;
bool screenFill = false;
DummyAnimation(BattleInterface & owner, int howManyFrames);
};
/// Class that plays effect at one or more positions along with (single) sound effect
class CPointEffectAnimation : public CBattleAnimation
class EffectAnimation : public BattleAnimation
{
soundBase::soundID sound;
bool soundPlayed;
bool soundFinished;
std::string soundName;
bool effectFinished;
int effectFlags;
@ -297,54 +318,50 @@ class CPointEffectAnimation : public CBattleAnimation
bool screenFill() const;
void onEffectFinished();
void onSoundFinished();
void clearEffect();
void playSound();
void playEffect();
public:
enum EEffectFlags
{
ALIGN_TO_BOTTOM = 1,
WAIT_FOR_SOUND = 2,
FORCE_ON_TOP = 4,
SCREEN_FILL = 8,
FORCE_ON_TOP = 2,
SCREEN_FILL = 4,
};
/// Create animation with screen-wide effect
CPointEffectAnimation(BattleInterface & owner, soundBase::soundID sound, std::string animationName, int effects = 0);
EffectAnimation(BattleInterface & owner, std::string animationName, int effects = 0);
/// Create animation positioned at point(s). Note that positions must be are absolute, including battleint position offset
CPointEffectAnimation(BattleInterface & owner, soundBase::soundID sound, std::string animationName, Point pos , int effects = 0);
CPointEffectAnimation(BattleInterface & owner, soundBase::soundID sound, std::string animationName, std::vector<Point> pos , int effects = 0);
EffectAnimation(BattleInterface & owner, std::string animationName, Point pos , int effects = 0);
EffectAnimation(BattleInterface & owner, std::string animationName, std::vector<Point> pos , int effects = 0);
/// Create animation positioned at certain hex(es)
CPointEffectAnimation(BattleInterface & owner, soundBase::soundID sound, std::string animationName, BattleHex hex , int effects = 0);
CPointEffectAnimation(BattleInterface & owner, soundBase::soundID sound, std::string animationName, std::vector<BattleHex> hex, int effects = 0);
EffectAnimation(BattleInterface & owner, std::string animationName, BattleHex hex , int effects = 0);
EffectAnimation(BattleInterface & owner, std::string animationName, std::vector<BattleHex> hex, int effects = 0);
CPointEffectAnimation(BattleInterface & owner, soundBase::soundID sound, std::string animationName, Point pos, BattleHex hex, int effects = 0);
~CPointEffectAnimation();
EffectAnimation(BattleInterface & owner, std::string animationName, Point pos, BattleHex hex, int effects = 0);
~EffectAnimation();
bool init() override;
void nextFrame() override;
};
/// Base class (e.g. for use in dynamic_cast's) for "animations" that wait for certain event
class CWaitingAnimation : public CBattleAnimation
class HeroCastAnimation : public BattleAnimation
{
protected:
CWaitingAnimation(BattleInterface & owner);
std::shared_ptr<BattleHero> hero;
const CStack * target;
const CSpell * spell;
BattleHex tile;
bool projectileEmitted;
void initializeProjectile();
void emitProjectile();
void emitAnimationEvent();
public:
HeroCastAnimation(BattleInterface & owner, std::shared_ptr<BattleHero> hero, BattleHex dest, const CStack * defender, const CSpell * spell);
void nextFrame() override;
};
/// Class that waits till projectile of certain shooter hits a target
class CWaitingProjectileAnimation : public CWaitingAnimation
{
const CStack * shooter;
public:
CWaitingProjectileAnimation(BattleInterface & owner, const CStack * shooter);
bool init() override;
};

View File

@ -0,0 +1,92 @@
/*
* BattleConstants.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
enum class EBattleEffect
{
// list of battle effects that have hardcoded triggers
MAGIC_MIRROR = 3,
FIRE_SHIELD = 11,
FEAR = 15,
GOOD_LUCK = 18,
GOOD_MORALE = 20,
BAD_MORALE = 30,
BAD_LUCK = 48,
RESURRECT = 50,
DRAIN_LIFE = 52,
POISON = 67,
DEATH_BLOW = 73,
REGENERATION = 74,
MANA_DRAIN = 77,
RESISTANCE = 78,
INVALID = -1,
};
enum class EAnimationEvents {
OPENING = 0, // battle opening sound is playing
ACTION = 1, // there are any ongoing animations
MOVEMENT = 2, // stacks are moving or turning around
BEFORE_HIT = 3, // effects played before all attack/defence/hit animations
ATTACK = 4, // attack and defence animations are playing
HIT = 5, // hit & death animations are playing
AFTER_HIT = 6, // after all hit & death animations are over
COUNT
};
enum class EHeroAnimType
{
HOLDING = 0,
IDLE = 1, // idling movement that happens from time to time
DEFEAT = 2, // played when army loses stack or on friendly fire
VICTORY = 3, // when enemy stack killed or huge damage is dealt
CAST_SPELL = 4 // spellcasting
};
enum class ECreatureAnimType
{
INVALID = -1,
MOVING = 0,
MOUSEON = 1,
HOLDING = 2, // base idling animation
HITTED = 3, // base animation for when stack is taking damage
DEFENCE = 4, // alternative animation for defending in melee if stack spent its action on defending
DEATH = 5,
DEATH_RANGED = 6, // Optional, alternative animation for when stack is killed by ranged attack
TURN_L = 7,
TURN_R = 8,
//TURN_L2 = 9, //unused - identical to TURN_L
//TURN_R2 = 10, //unused - identical to TURN_R
ATTACK_UP = 11,
ATTACK_FRONT = 12,
ATTACK_DOWN = 13,
SHOOT_UP = 14, // Shooters only
SHOOT_FRONT = 15, // Shooters only
SHOOT_DOWN = 16, // Shooters only
SPECIAL_UP = 17, // If empty, fallback to SPECIAL_FRONT
SPECIAL_FRONT = 18, // Used for any special moves - dragon breath, spellcasting, Pit Lord/Ogre Mage ability
SPECIAL_DOWN = 19, // If empty, fallback to SPECIAL_FRONT
MOVE_START = 20, // small animation to be played before MOVING
MOVE_END = 21, // small animation to be played after MOVING
DEAD = 22, // new group, used to show dead stacks. If empty - last frame from "DEATH" will be copied here
DEAD_RANGED = 23, // new group, used to show dead stacks (if DEATH_RANGED was used). If empty - last frame from "DEATH_RANGED" will be copied here
RESURRECTION = 24, // new group, used for animating resurrection, if empty - reversed "DEATH" animation will be copiend here
FROZEN = 25, // new group, used when stack animation is paused (e.g. petrified). If empty - consist of first frame from HOLDING animation
CAST_UP = 30,
CAST_FRONT = 31,
CAST_DOWN = 32,
GROUP_ATTACK_UP = 40,
GROUP_ATTACK_FRONT = 41,
GROUP_ATTACK_DOWN = 42
};

View File

@ -11,7 +11,7 @@
#include "BattleEffectsController.h"
#include "BattleAnimationClasses.h"
#include "BattleControlPanel.h"
#include "BattleWindow.h"
#include "BattleInterface.h"
#include "BattleInterfaceClasses.h"
#include "BattleFieldController.h"
@ -26,6 +26,7 @@
#include "../../CCallback.h"
#include "../../lib/battle/BattleAction.h"
#include "../../lib/filesystem/ResourceID.h"
#include "../../lib/NetPacks.h"
#include "../../lib/CStack.h"
#include "../../lib/IGameEventsReceiver.h"
@ -33,35 +34,30 @@
BattleEffectsController::BattleEffectsController(BattleInterface & owner):
owner(owner)
{}
void BattleEffectsController::displayEffect(EBattleEffect::EBattleEffect effect, const BattleHex & destTile)
{
displayEffect(effect, soundBase::invalid, destTile);
loadColorMuxers();
}
void BattleEffectsController::displayEffect(EBattleEffect::EBattleEffect effect, uint32_t soundID, const BattleHex & destTile)
void BattleEffectsController::displayEffect(EBattleEffect effect, const BattleHex & destTile)
{
std::string customAnim = graphics->battleACToDef[effect][0];
owner.stacksController->addNewAnim(new CPointEffectAnimation(owner, soundBase::soundID(soundID), customAnim, destTile));
displayEffect(effect, "", destTile);
}
void BattleEffectsController::displayCustomEffects(const std::vector<CustomEffectInfo> & customEffects)
void BattleEffectsController::displayEffect(EBattleEffect effect, std::string soundFile, const BattleHex & destTile)
{
for(const CustomEffectInfo & one : customEffects)
{
const CStack * s = owner.curInt->cb->battleGetStackByID(one.stack, false);
size_t effectID = static_cast<size_t>(effect);
assert(s);
assert(one.effect != 0);
std::string customAnim = graphics->battleACToDef[effectID][0];
displayEffect(EBattleEffect::EBattleEffect(one.effect), soundBase::soundID(one.sound), s->getPosition());
}
CCS->soundh->playSound( soundFile );
owner.stacksController->addNewAnim(new EffectAnimation(owner, customAnim, destTile));
}
void BattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & bte)
{
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);
const CStack * stack = owner.curInt->cb->battleGetStackByID(bte.stackID);
if(!stack)
{
@ -71,45 +67,46 @@ void BattleEffectsController::battleTriggerEffect(const BattleTriggerEffect & bt
//don't show animation when no HP is regenerated
switch(bte.effect)
{
//TODO: move to bonus type handler
case Bonus::HP_REGENERATION:
displayEffect(EBattleEffect::REGENERATION, soundBase::REGENER, stack->getPosition());
displayEffect(EBattleEffect::REGENERATION, "REGENER", stack->getPosition());
break;
case Bonus::MANA_DRAIN:
displayEffect(EBattleEffect::MANA_DRAIN, soundBase::MANADRAI, stack->getPosition());
displayEffect(EBattleEffect::MANA_DRAIN, "MANADRAI", stack->getPosition());
break;
case Bonus::POISON:
displayEffect(EBattleEffect::POISON, soundBase::POISON, stack->getPosition());
displayEffect(EBattleEffect::POISON, "POISON", stack->getPosition());
break;
case Bonus::FEAR:
displayEffect(EBattleEffect::FEAR, soundBase::FEAR, stack->getPosition());
displayEffect(EBattleEffect::FEAR, "FEAR", stack->getPosition());
break;
case Bonus::MORALE:
{
std::string hlp = CGI->generaltexth->allTexts[33];
boost::algorithm::replace_first(hlp,"%s",(stack->getName()));
displayEffect(EBattleEffect::GOOD_MORALE, soundBase::GOODMRLE, stack->getPosition());
owner.controlPanel->console->addText(hlp);
displayEffect(EBattleEffect::GOOD_MORALE, "GOODMRLE", stack->getPosition());
owner.appendBattleLog(hlp);
break;
}
default:
return;
}
//waitForAnims(); //fixme: freezes game :?
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
}
void BattleEffectsController::startAction(const BattleAction* action)
{
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);
const CStack *stack = owner.curInt->cb->battleGetStackByID(action->stackNumber);
switch(action->actionType)
{
case EActionType::WAIT:
owner.controlPanel->console->addText(stack->formatGeneralMessage(136));
owner.appendBattleLog(stack->formatGeneralMessage(136));
break;
case EActionType::BAD_MORALE:
owner.controlPanel->console->addText(stack->formatGeneralMessage(-34));
displayEffect(EBattleEffect::BAD_MORALE, soundBase::BADMRLE, stack->getPosition());
owner.appendBattleLog(stack->formatGeneralMessage(-34));
displayEffect(EBattleEffect::BAD_MORALE, "BADMRLE", stack->getPosition());
break;
}
@ -118,23 +115,54 @@ void BattleEffectsController::startAction(const BattleAction* action)
switch(action->actionType)
{
case EActionType::STACK_HEAL:
displayEffect(EBattleEffect::REGENERATION, soundBase::REGENER, actionTarget.at(0).hexValue);
displayEffect(EBattleEffect::REGENERATION, "REGENER", actionTarget.at(0).hexValue);
break;
}
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
}
void BattleEffectsController::collectRenderableObjects(BattleRenderer & renderer)
{
for (auto & elem : battleEffects)
{
renderer.insert( EBattleFieldLayer::EFFECTS, elem.position, [&elem](BattleRenderer::RendererRef canvas)
renderer.insert( EBattleFieldLayer::EFFECTS, elem.tile, [&elem](BattleRenderer::RendererRef canvas)
{
int currentFrame = static_cast<int>(floor(elem.currentFrame));
currentFrame %= elem.animation->size();
auto img = elem.animation->getImage(currentFrame);
canvas.draw(img, Point(elem.x, elem.y));
canvas.draw(img, elem.pos);
});
}
}
void BattleEffectsController::loadColorMuxers()
{
const JsonNode config(ResourceID("config/battleEffects.json"));
for(auto & muxer : config["colorMuxers"].Struct())
{
ColorMuxerEffect effect;
std::string identifier = muxer.first;
for (const JsonNode & entry : muxer.second.Vector() )
{
effect.timePoints.push_back(entry["time"].Float());
effect.filters.push_back(ColorFilter::genFromJson(entry));
}
colorMuxerEffects[identifier] = effect;
}
}
const ColorMuxerEffect & BattleEffectsController::getMuxerEffect(const std::string & name)
{
static const ColorMuxerEffect emptyEffect;
if (colorMuxerEffects.count(name))
return colorMuxerEffects[name];
logAnim->error("Failed to find color muxer effect named '%s'!", name);
return emptyEffect;
}

View File

@ -10,50 +10,31 @@
#pragma once
#include "../../lib/battle/BattleHex.h"
#include "../gui/Geometries.h"
#include "BattleConstants.h"
VCMI_LIB_NAMESPACE_BEGIN
class BattleAction;
struct CustomEffectInfo;
struct BattleTriggerEffect;
VCMI_LIB_NAMESPACE_END
struct ColorMuxerEffect;
class CAnimation;
class Canvas;
class BattleInterface;
class BattleRenderer;
class CPointEffectAnimation;
namespace EBattleEffect
{
enum EBattleEffect
{
// list of battle effects that have hardcoded triggers
FEAR = 15,
GOOD_LUCK = 18,
GOOD_MORALE = 20,
BAD_MORALE = 30,
BAD_LUCK = 48,
RESURRECT = 50,
DRAIN_LIFE = 52, // hardcoded constant in CGameHandler
POISON = 67,
DEATH_BLOW = 73,
REGENERATION = 74,
MANA_DRAIN = 77,
INVALID = -1,
};
}
class EffectAnimation;
/// Struct for battle effect animation e.g. morale, prayer, armageddon, bless,...
struct BattleEffect
{
int x, y; //position on the screen
Point pos; //position on the screen
float currentFrame;
std::shared_ptr<CAnimation> animation;
int effectID; //uniqueID equal ot ID of appropriate CSpellEffectAnim
BattleHex position; //Indicates if effect which hex the effect is drawn on
BattleHex tile; //Indicates if effect which hex the effect is drawn on
};
/// Controls rendering of effects in battle, e.g. from spells, abilities and various other actions like morale
@ -64,21 +45,23 @@ class BattleEffectsController
/// list of current effects that are being displayed on screen (spells & creature abilities)
std::vector<BattleEffect> battleEffects;
std::map<std::string, ColorMuxerEffect> colorMuxerEffects;
void loadColorMuxers();
public:
const ColorMuxerEffect &getMuxerEffect(const std::string & name);
BattleEffectsController(BattleInterface & owner);
void startAction(const BattleAction* action);
void displayCustomEffects(const std::vector<CustomEffectInfo> & customEffects);
//displays custom effect on the battlefield
void displayEffect(EBattleEffect::EBattleEffect effect, const BattleHex & destTile);
void displayEffect(EBattleEffect::EBattleEffect effect, uint32_t soundID, const BattleHex & destTile);
//void displayEffects(EBattleEffect::EBattleEffect effect, uint32_t soundID, const std::vector<BattleHex> & destTiles);
void displayEffect(EBattleEffect effect, const BattleHex & destTile);
void displayEffect(EBattleEffect effect, std::string soundFile, const BattleHex & destTile);
void battleTriggerEffect(const BattleTriggerEffect & bte);
void collectRenderableObjects(BattleRenderer & renderer);
friend class CPointEffectAnimation;
friend class EffectAnimation;
};

View File

@ -22,6 +22,7 @@
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../widgets/AdventureMapClasses.h"
#include "../gui/CAnimation.h"
#include "../gui/Canvas.h"
#include "../gui/CGuiHandler.h"
@ -34,12 +35,10 @@
#include "../../lib/spells/ISpellMechanics.h"
BattleFieldController::BattleFieldController(BattleInterface & owner):
owner(owner),
attackingHex(BattleHex::INVALID)
owner(owner)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
pos.w = owner.pos.w;
pos.h = owner.pos.h;
strongInterest = true;
//preparing cells and hexes
cellBorder = IImage::createFromFile("CCELLGRD.BMP");
@ -59,6 +58,8 @@ BattleFieldController::BattleFieldController(BattleInterface & owner):
std::string backgroundName = owner.siegeController->getBattleBackgroundName();
background = IImage::createFromFile(backgroundName);
}
pos.w = background->width();
pos.h = background->height();
//preparing graphic with cell borders
cellBorders = std::make_unique<Canvas>(Point(background->width(), background->height()));
@ -87,17 +88,42 @@ BattleFieldController::BattleFieldController(BattleInterface & owner):
auto accessibility = owner.curInt->cb->getAccesibility();
for(int i = 0; i < accessibility.size(); i++)
stackCountOutsideHexes[i] = (accessibility[i] == EAccessibility::ACCESSIBLE);
addUsedEvents(MOVE);
LOCPLINT->cingconsole->pos = this->pos;
}
void BattleFieldController::createHeroes()
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
// create heroes as part of our constructor for correct positioning inside battlefield
if(owner.attackingHeroInstance)
owner.attackingHero = std::make_shared<BattleHero>(owner, owner.attackingHeroInstance, false);
if(owner.defendingHeroInstance)
owner.defendingHero = std::make_shared<BattleHero>(owner, owner.defendingHeroInstance, true);
}
void BattleFieldController::mouseMoved(const SDL_MouseMotionEvent &event)
{
BattleHex selectedHex = getHoveredHex();
owner.actionsController->handleHex(selectedHex, MOVE);
}
void BattleFieldController::renderBattlefield(Canvas & canvas)
{
showBackground(canvas);
Canvas clippedCanvas(canvas, pos);
showBackground(clippedCanvas);
BattleRenderer renderer(owner);
renderer.execute(canvas);
renderer.execute(clippedCanvas);
owner.projectilesController->showProjectiles(canvas);
owner.projectilesController->showProjectiles(clippedCanvas);
}
void BattleFieldController::showBackground(Canvas & canvas)
@ -113,19 +139,19 @@ void BattleFieldController::showBackground(Canvas & canvas)
void BattleFieldController::showBackgroundImage(Canvas & canvas)
{
canvas.draw(background, owner.pos.topLeft());
canvas.draw(background, Point(0, 0));
owner.obstacleController->showAbsoluteObstacles(canvas, pos.topLeft());
owner.obstacleController->showAbsoluteObstacles(canvas);
if ( owner.siegeController )
owner.siegeController->showAbsoluteObstacles(canvas, pos.topLeft());
owner.siegeController->showAbsoluteObstacles(canvas);
if (settings["battle"]["cellBorders"].Bool())
canvas.draw(*cellBorders, owner.pos.topLeft());
canvas.draw(*cellBorders, Point(0, 0));
}
void BattleFieldController::showBackgroundImageWithHexes(Canvas & canvas)
{
canvas.draw(*backgroundWithHexes.get(), owner.pos.topLeft());
canvas.draw(*backgroundWithHexes.get(), Point(0, 0));
}
void BattleFieldController::redrawBackgroundWithHexes()
@ -142,9 +168,9 @@ void BattleFieldController::redrawBackgroundWithHexes()
//prepare background graphic with hexes and shaded hexes
backgroundWithHexes->draw(background, Point(0,0));
owner.obstacleController->showAbsoluteObstacles(*backgroundWithHexes, Point(0,0));
owner.obstacleController->showAbsoluteObstacles(*backgroundWithHexes);
if ( owner.siegeController )
owner.siegeController->showAbsoluteObstacles(*backgroundWithHexes, Point(0,0));
owner.siegeController->showAbsoluteObstacles(*backgroundWithHexes);
if (settings["battle"]["stackRange"].Bool())
{
@ -162,7 +188,7 @@ void BattleFieldController::redrawBackgroundWithHexes()
void BattleFieldController::showHighlightedHex(Canvas & canvas, BattleHex hex, bool darkBorder)
{
Point hexPos = hexPositionAbsolute(hex).topLeft();
Point hexPos = hexPositionLocal(hex).topLeft();
canvas.draw(cellShade, hexPos);
if(!darkBorder && settings["battle"]["cellBorders"].Bool())
@ -233,19 +259,58 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesSpellRange()
result.insert(shadedHex);
}
}
else if(owner.active) //always highlight pointed hex
{
if(hoveredHex.getX() != 0 && hoveredHex.getX() != GameConstants::BFIELD_WIDTH - 1)
result.insert(hoveredHex);
}
return result;
}
std::set<BattleHex> BattleFieldController::getHighlightedHexesMovementTarget()
{
const CStack * stack = owner.stacksController->getActiveStack();
auto hoveredHex = getHoveredHex();
if (stack)
{
std::vector<BattleHex> v = owner.curInt->cb->battleGetAvailableHexes(stack, false, nullptr);
auto hoveredStack = owner.curInt->cb->battleGetStackByPos(hoveredHex, true);
if(owner.curInt->cb->battleCanAttack(stack, hoveredStack, hoveredHex))
{
if (isTileAttackable(hoveredHex))
{
BattleHex attackFromHex = fromWhichHexAttack(hoveredHex);
if (stack->doubleWide())
return {attackFromHex, stack->occupiedHex(attackFromHex)};
else
return {attackFromHex};
}
}
if (vstd::contains(v,hoveredHex))
{
if (stack->doubleWide())
return {hoveredHex, stack->occupiedHex(hoveredHex)};
else
return {hoveredHex};
}
if (stack->doubleWide())
{
for (auto const & hex : v)
{
if (stack->occupiedHex(hex) == hoveredHex)
return { hoveredHex, hex };
}
}
}
return {};
}
void BattleFieldController::showHighlightedHexes(Canvas & canvas)
{
std::set<BattleHex> hoveredStack = getHighlightedHexesStackRange();
std::set<BattleHex> hoveredMouse = getHighlightedHexesSpellRange();
std::set<BattleHex> hoveredSpell = getHighlightedHexesSpellRange();
std::set<BattleHex> hoveredMove = getHighlightedHexesMovementTarget();
auto const & hoveredMouse = owner.actionsController->spellcastingModeActive() ? hoveredSpell : hoveredMove;
for(int b=0; b<GameConstants::BFIELD_SIZE; ++b)
{
@ -280,7 +345,7 @@ Rect BattleFieldController::hexPositionLocal(BattleHex hex) const
Rect BattleFieldController::hexPositionAbsolute(BattleHex hex) const
{
return hexPositionLocal(hex) + owner.pos.topLeft();
return hexPositionLocal(hex) + pos.topLeft();
}
bool BattleFieldController::isPixelInHex(Point const & position)
@ -299,323 +364,165 @@ BattleHex BattleFieldController::getHoveredHex()
void BattleFieldController::setBattleCursor(BattleHex myNumber)
{
Rect hoveredHexPos = hexPositionAbsolute(myNumber);
CCursorHandler *cursor = CCS->curh;
Point cursorPos = CCS->curh->position();
const double subdividingAngle = 2.0*M_PI/6.0; // Divide a hex into six sectors.
const double hexMidX = hoveredHexPos.x + hoveredHexPos.w/2.0;
const double hexMidY = hoveredHexPos.y + hoveredHexPos.h/2.0;
const double cursorHexAngle = M_PI - atan2(hexMidY - cursor->ypos, cursor->xpos - hexMidX) + subdividingAngle/2; //TODO: refactor this nightmare
const double sector = fmod(cursorHexAngle/subdividingAngle, 6.0);
const int zigzagCorrection = !((myNumber/GameConstants::BFIELD_WIDTH)%2); // Off-by-one correction needed to deal with the odd battlefield rows.
std::vector<Cursor::Combat> sectorCursor = {
Cursor::Combat::HIT_SOUTHEAST,
Cursor::Combat::HIT_SOUTHWEST,
Cursor::Combat::HIT_WEST,
Cursor::Combat::HIT_NORTHWEST,
Cursor::Combat::HIT_NORTHEAST,
Cursor::Combat::HIT_EAST,
Cursor::Combat::HIT_SOUTH,
Cursor::Combat::HIT_NORTH,
};
std::vector<int> sectorCursor; // From left to bottom left.
sectorCursor.push_back(8);
sectorCursor.push_back(9);
sectorCursor.push_back(10);
sectorCursor.push_back(11);
sectorCursor.push_back(12);
sectorCursor.push_back(7);
auto direction = static_cast<size_t>(selectAttackDirection(myNumber, cursorPos));
const bool doubleWide = owner.stacksController->getActiveStack()->doubleWide();
bool aboveAttackable = true, belowAttackable = true;
// Exclude directions which cannot be attacked from.
// Check to the left.
if (myNumber%GameConstants::BFIELD_WIDTH <= 1 || !vstd::contains(occupyableHexes, myNumber - 1))
{
sectorCursor[0] = -1;
}
// Check top left, top right as well as above for 2-hex creatures.
if (myNumber/GameConstants::BFIELD_WIDTH == 0)
{
sectorCursor[1] = -1;
sectorCursor[2] = -1;
aboveAttackable = false;
}
else
{
if (doubleWide)
{
bool attackRow[4] = {true, true, true, true};
if (myNumber%GameConstants::BFIELD_WIDTH <= 1 || !vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH - 2 + zigzagCorrection))
attackRow[0] = false;
if (!vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection))
attackRow[1] = false;
if (!vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH + zigzagCorrection))
attackRow[2] = false;
if (myNumber%GameConstants::BFIELD_WIDTH >= GameConstants::BFIELD_WIDTH - 2 || !vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH + 1 + zigzagCorrection))
attackRow[3] = false;
if (!(attackRow[0] && attackRow[1]))
sectorCursor[1] = -1;
if (!(attackRow[1] && attackRow[2]))
aboveAttackable = false;
if (!(attackRow[2] && attackRow[3]))
sectorCursor[2] = -1;
}
else
{
if (!vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection))
sectorCursor[1] = -1;
if (!vstd::contains(occupyableHexes, myNumber - GameConstants::BFIELD_WIDTH + zigzagCorrection))
sectorCursor[2] = -1;
}
}
// Check to the right.
if (myNumber%GameConstants::BFIELD_WIDTH >= GameConstants::BFIELD_WIDTH - 2 || !vstd::contains(occupyableHexes, myNumber + 1))
{
sectorCursor[3] = -1;
}
// Check bottom right, bottom left as well as below for 2-hex creatures.
if (myNumber/GameConstants::BFIELD_WIDTH == GameConstants::BFIELD_HEIGHT - 1)
{
sectorCursor[4] = -1;
sectorCursor[5] = -1;
belowAttackable = false;
}
else
{
if (doubleWide)
{
bool attackRow[4] = {true, true, true, true};
if (myNumber%GameConstants::BFIELD_WIDTH <= 1 || !vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH - 2 + zigzagCorrection))
attackRow[0] = false;
if (!vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection))
attackRow[1] = false;
if (!vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH + zigzagCorrection))
attackRow[2] = false;
if (myNumber%GameConstants::BFIELD_WIDTH >= GameConstants::BFIELD_WIDTH - 2 || !vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH + 1 + zigzagCorrection))
attackRow[3] = false;
if (!(attackRow[0] && attackRow[1]))
sectorCursor[5] = -1;
if (!(attackRow[1] && attackRow[2]))
belowAttackable = false;
if (!(attackRow[2] && attackRow[3]))
sectorCursor[4] = -1;
}
else
{
if (!vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH + zigzagCorrection))
sectorCursor[4] = -1;
if (!vstd::contains(occupyableHexes, myNumber + GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection))
sectorCursor[5] = -1;
}
}
// Determine index from sector.
int cursorIndex;
if (doubleWide)
{
sectorCursor.insert(sectorCursor.begin() + 5, belowAttackable ? 13 : -1);
sectorCursor.insert(sectorCursor.begin() + 2, aboveAttackable ? 14 : -1);
if (sector < 1.5)
cursorIndex = static_cast<int>(sector);
else if (sector >= 1.5 && sector < 2.5)
cursorIndex = 2;
else if (sector >= 2.5 && sector < 4.5)
cursorIndex = (int) sector + 1;
else if (sector >= 4.5 && sector < 5.5)
cursorIndex = 6;
else
cursorIndex = (int) sector + 2;
}
else
{
cursorIndex = static_cast<int>(sector);
}
// Generally should NEVER happen, but to avoid the possibility of having endless loop below... [#1016]
if (!vstd::contains_if (sectorCursor, [](int sc) { return sc != -1; }))
{
logGlobal->error("Error: for hex %d cannot find a hex to attack from!", myNumber);
attackingHex = -1;
return;
}
// Find the closest direction attackable, starting with the right one.
// FIXME: Is this really how the original H3 client does it?
int i = 0;
while (sectorCursor[(cursorIndex + i)%sectorCursor.size()] == -1) //Why hast thou forsaken me?
i = i <= 0 ? 1 - i : -i; // 0, 1, -1, 2, -2, 3, -3 etc..
int index = (cursorIndex + i)%sectorCursor.size(); //hopefully we get elements from sectorCursor
cursor->changeGraphic(ECursor::COMBAT, sectorCursor[index]);
switch (index)
{
case 0:
attackingHex = myNumber - 1; //left
break;
case 1:
attackingHex = myNumber - GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection; //top left
break;
case 2:
attackingHex = myNumber - GameConstants::BFIELD_WIDTH + zigzagCorrection; //top right
break;
case 3:
attackingHex = myNumber + 1; //right
break;
case 4:
attackingHex = myNumber + GameConstants::BFIELD_WIDTH + zigzagCorrection; //bottom right
break;
case 5:
attackingHex = myNumber + GameConstants::BFIELD_WIDTH - 1 + zigzagCorrection; //bottom left
break;
}
BattleHex hex(attackingHex);
if (!hex.isValid())
attackingHex = -1;
assert(direction != -1);
if (direction != -1)
CCS->curh->set(sectorCursor[direction]);
}
BattleHex BattleFieldController::fromWhichHexAttack(BattleHex myNumber)
BattleHex::EDir BattleFieldController::selectAttackDirection(BattleHex myNumber, const Point & cursorPos)
{
//TODO far too much repeating code
BattleHex destHex;
switch(CCS->curh->frame)
const bool doubleWide = owner.stacksController->getActiveStack()->doubleWide();
auto neighbours = myNumber.allNeighbouringTiles();
// 0 1
// 5 x 2
// 4 3
// if true - our current stack can move into this hex (and attack)
std::array<bool, 8> attackAvailability;
if (doubleWide)
{
case 12: //from bottom right
// For double-hexes we need to ensure that both hexes needed for this direction are occupyable:
// | -0- | -1- | -2- | -3- | -4- | -5- | -6- | -7-
// | o o - | - o o | - - | - - | - - | - - | o o | - -
// | - x - | - x - | - x o o| - x - | - x - |o o x - | - x - | - x -
// | - - | - - | - - | - o o | o o - | - - | - - | o o
for (size_t i : { 1, 2, 3})
attackAvailability[i] = vstd::contains(occupyableHexes, neighbours[i]) && vstd::contains(occupyableHexes, neighbours[i].cloneInDirection(BattleHex::RIGHT, false));
for (size_t i : { 4, 5, 0})
attackAvailability[i] = vstd::contains(occupyableHexes, neighbours[i]) && vstd::contains(occupyableHexes, neighbours[i].cloneInDirection(BattleHex::LEFT, false));
attackAvailability[6] = vstd::contains(occupyableHexes, neighbours[0]) && vstd::contains(occupyableHexes, neighbours[1]);
attackAvailability[7] = vstd::contains(occupyableHexes, neighbours[3]) && vstd::contains(occupyableHexes, neighbours[4]);
}
else
{
for (size_t i = 0; i < 6; ++i)
attackAvailability[i] = vstd::contains(occupyableHexes, neighbours[i]);
attackAvailability[6] = false;
attackAvailability[7] = false;
}
// Zero available tiles to attack from
if ( vstd::find(attackAvailability, true) == attackAvailability.end())
{
logGlobal->error("Error: cannot find a hex to attack hex %d from!", myNumber);
return BattleHex::NONE;
}
// For each valid direction, select position to test against
std::array<Point, 8> testPoint;
for (size_t i = 0; i < 6; ++i)
if (attackAvailability[i])
testPoint[i] = hexPositionAbsolute(neighbours[i]).center();
// For bottom/top directions select central point, but move it a bit away from true center to reduce zones allocated to them
if (attackAvailability[6])
testPoint[6] = (hexPositionAbsolute(neighbours[0]).center() + hexPositionAbsolute(neighbours[1]).center()) / 2 + Point(0, -5);
if (attackAvailability[7])
testPoint[7] = (hexPositionAbsolute(neighbours[3]).center() + hexPositionAbsolute(neighbours[4]).center()) / 2 + Point(0, 5);
// Compute distance between tested position & cursor position and pick nearest
std::array<int, 8> distance2;
for (size_t i = 0; i < 8; ++i)
if (attackAvailability[i])
distance2[i] = (testPoint[i].y - cursorPos.y)*(testPoint[i].y - cursorPos.y) + (testPoint[i].x - cursorPos.x)*(testPoint[i].x - cursorPos.x);
size_t nearest = -1;
for (size_t i = 0; i < 8; ++i)
if (attackAvailability[i] && (nearest == -1 || distance2[i] < distance2[nearest]) )
nearest = i;
assert(nearest != -1);
return BattleHex::EDir(nearest);
}
BattleHex BattleFieldController::fromWhichHexAttack(BattleHex attackTarget)
{
BattleHex::EDir direction = selectAttackDirection(attackTarget, CCS->curh->position());
const CStack * attacker = owner.stacksController->getActiveStack();
assert(direction != BattleHex::NONE);
assert(attacker);
if (!attacker->doubleWide())
{
assert(direction != BattleHex::BOTTOM);
assert(direction != BattleHex::TOP);
return attackTarget.cloneInDirection(direction);
}
else
{
// We need to find position of right hex of double-hex creature (or left for defending side)
// | TOP_LEFT |TOP_RIGHT | RIGHT |BOTTOM_RIGHT|BOTTOM_LEFT| LEFT | TOP |BOTTOM
// | o o - | - o o | - - | - - | - - | - - | o o | - -
// | - x - | - x - | - x o o| - x - | - x - |o o x - | - x - | - x -
// | - - | - - | - - | - o o | o o - | - - | - - | o o
switch (direction)
{
bool doubleWide = owner.stacksController->getActiveStack()->doubleWide();
destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH+1 ) +
(owner.stacksController->getActiveStack()->side == BattleSide::ATTACKER && doubleWide ? 1 : 0);
if(vstd::contains(occupyableHexes, destHex))
return destHex;
else if(owner.stacksController->getActiveStack()->side == BattleSide::ATTACKER)
{
if (vstd::contains(occupyableHexes, destHex+1))
return destHex+1;
}
else //if we are defender
{
if(vstd::contains(occupyableHexes, destHex-1))
return destHex-1;
}
break;
}
case 7: //from bottom left
case BattleHex::TOP_LEFT:
case BattleHex::LEFT:
case BattleHex::BOTTOM_LEFT:
{
destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH-1 : GameConstants::BFIELD_WIDTH );
if (vstd::contains(occupyableHexes, destHex))
return destHex;
else if(owner.stacksController->getActiveStack()->side == BattleSide::ATTACKER)
{
if(vstd::contains(occupyableHexes, destHex+1))
return destHex+1;
}
else //we are defender
{
if(vstd::contains(occupyableHexes, destHex-1))
return destHex-1;
}
break;
}
case 8: //from left
{
if(owner.stacksController->getActiveStack()->doubleWide() && owner.stacksController->getActiveStack()->side == BattleSide::DEFENDER)
{
std::vector<BattleHex> acc = owner.curInt->cb->battleGetAvailableHexes(owner.stacksController->getActiveStack());
if (vstd::contains(acc, myNumber))
return myNumber - 1;
else
return myNumber - 2;
}
if ( attacker->side == BattleSide::ATTACKER )
return attackTarget.cloneInDirection(direction);
else
{
return myNumber - 1;
}
break;
return attackTarget.cloneInDirection(direction).cloneInDirection(BattleHex::LEFT);
}
case 9: //from top left
case BattleHex::TOP_RIGHT:
case BattleHex::RIGHT:
case BattleHex::BOTTOM_RIGHT:
{
destHex = myNumber - ((myNumber/GameConstants::BFIELD_WIDTH) % 2 ? GameConstants::BFIELD_WIDTH + 1 : GameConstants::BFIELD_WIDTH);
if(vstd::contains(occupyableHexes, destHex))
return destHex;
else if(owner.stacksController->getActiveStack()->side == BattleSide::ATTACKER)
{
if(vstd::contains(occupyableHexes, destHex+1))
return destHex+1;
}
else //if we are defender
{
if(vstd::contains(occupyableHexes, destHex-1))
return destHex-1;
}
break;
}
case 10: //from top right
{
bool doubleWide = owner.stacksController->getActiveStack()->doubleWide();
destHex = myNumber - ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH-1 ) +
(owner.stacksController->getActiveStack()->side == BattleSide::ATTACKER && doubleWide ? 1 : 0);
if(vstd::contains(occupyableHexes, destHex))
return destHex;
else if(owner.stacksController->getActiveStack()->side == BattleSide::ATTACKER)
{
if(vstd::contains(occupyableHexes, destHex+1))
return destHex+1;
}
else //if we are defender
{
if(vstd::contains(occupyableHexes, destHex-1))
return destHex-1;
}
break;
}
case 11: //from right
{
if(owner.stacksController->getActiveStack()->doubleWide() && owner.stacksController->getActiveStack()->side == BattleSide::ATTACKER)
{
std::vector<BattleHex> acc = owner.curInt->cb->battleGetAvailableHexes(owner.stacksController->getActiveStack());
if(vstd::contains(acc, myNumber))
return myNumber + 1;
else
return myNumber + 2;
}
if ( attacker->side == BattleSide::ATTACKER )
return attackTarget.cloneInDirection(direction).cloneInDirection(BattleHex::RIGHT);
else
{
return myNumber + 1;
}
break;
return attackTarget.cloneInDirection(direction);
}
case 13: //from bottom
case BattleHex::TOP:
{
destHex = myNumber + ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH+1 );
if(vstd::contains(occupyableHexes, destHex))
return destHex;
else if(owner.stacksController->getActiveStack()->side == BattleSide::ATTACKER)
{
if(vstd::contains(occupyableHexes, destHex+1))
return destHex+1;
}
else //if we are defender
{
if(vstd::contains(occupyableHexes, destHex-1))
return destHex-1;
}
break;
if ( attacker->side == BattleSide::ATTACKER )
return attackTarget.cloneInDirection(BattleHex::TOP_RIGHT);
else
return attackTarget.cloneInDirection(BattleHex::TOP_LEFT);
}
case 14: //from top
case BattleHex::BOTTOM:
{
destHex = myNumber - ( (myNumber/GameConstants::BFIELD_WIDTH)%2 ? GameConstants::BFIELD_WIDTH : GameConstants::BFIELD_WIDTH-1 );
if (vstd::contains(occupyableHexes, destHex))
return destHex;
else if(owner.stacksController->getActiveStack()->side == BattleSide::ATTACKER)
{
if(vstd::contains(occupyableHexes, destHex+1))
return destHex+1;
}
else //if we are defender
{
if(vstd::contains(occupyableHexes, destHex-1))
return destHex-1;
}
break;
if ( attacker->side == BattleSide::ATTACKER )
return attackTarget.cloneInDirection(BattleHex::BOTTOM_RIGHT);
else
return attackTarget.cloneInDirection(BattleHex::BOTTOM_LEFT);
}
default:
assert(0);
return attackTarget.cloneInDirection(BattleHex::LEFT);
}
}
return -1;
}
bool BattleFieldController::isTileAttackable(const BattleHex & number) const
@ -632,3 +539,18 @@ bool BattleFieldController::stackCountOutsideHex(const BattleHex & number) const
{
return stackCountOutsideHexes[number];
}
void BattleFieldController::showAll(SDL_Surface * to)
{
show(to);
}
void BattleFieldController::show(SDL_Surface * to)
{
owner.stacksController->update();
owner.obstacleController->update();
Canvas canvas(to);
renderBattlefield(canvas);
}

View File

@ -22,6 +22,7 @@ struct Rect;
struct Point;
class ClickableHex;
class BattleHero;
class Canvas;
class IImage;
class BattleInterface;
@ -56,15 +57,24 @@ class BattleFieldController : public CIntObject
std::set<BattleHex> getHighlightedHexesStackRange();
std::set<BattleHex> getHighlightedHexesSpellRange();
std::set<BattleHex> getHighlightedHexesMovementTarget();
void showBackground(Canvas & canvas);
void showBackgroundImage(Canvas & canvas);
void showBackgroundImageWithHexes(Canvas & canvas);
void showHighlightedHexes(Canvas & canvas);
BattleHex::EDir selectAttackDirection(BattleHex myNumber, const Point & point);
void mouseMoved(const SDL_MouseMotionEvent &event) override;
void showAll(SDL_Surface * to) override;
void show(SDL_Surface * to) override;
public:
BattleFieldController(BattleInterface & owner);
void createHeroes();
void redrawBackgroundWithHexes();
void renderBattlefield(Canvas & canvas);

View File

@ -19,7 +19,7 @@
#include "BattleObstacleController.h"
#include "BattleSiegeController.h"
#include "BattleFieldController.h"
#include "BattleControlPanel.h"
#include "BattleWindow.h"
#include "BattleStacksController.h"
#include "BattleRenderer.h"
@ -42,18 +42,23 @@
#include "../../lib/NetPacks.h"
#include "../../lib/UnlockGuard.h"
CondSh<bool> BattleInterface::animsAreDisplayed(false);
CondSh<BattleAction *> BattleInterface::givenCommand(nullptr);
BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet *army2,
const CGHeroInstance *hero1, const CGHeroInstance *hero2,
const SDL_Rect & myRect,
std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt)
: attackingHeroInstance(hero1), defendingHeroInstance(hero2), animCount(0),
attackerInt(att), defenderInt(defen), curInt(att),
myTurn(false), moveStarted(false), moveSoundHander(-1), bresult(nullptr), battleActionsStarted(false)
std::shared_ptr<CPlayerInterface> att,
std::shared_ptr<CPlayerInterface> defen,
std::shared_ptr<CPlayerInterface> spectatorInt)
: attackingHeroInstance(hero1)
, defendingHeroInstance(hero2)
, attackerInt(att)
, defenderInt(defen)
, curInt(att)
, myTurn(false)
, moveSoundHander(-1)
{
OBJ_CONSTRUCTION;
for ( auto & event : animationEvents)
event.setn(false);
if(spectatorInt)
{
@ -65,9 +70,6 @@ BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet *
curInt = defenderInt;
}
animsAreDisplayed.setn(false);
pos = myRect;
strongInterest = true;
givenCommand.setn(nullptr);
//hot-seat -> check tactics for both players (defender may be local human)
@ -79,27 +81,6 @@ BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet *
//if we found interface of player with tactics, then enter tactics mode
tacticsMode = static_cast<bool>(tacticianInterface);
//create stack queue
bool embedQueue;
std::string queueSize = settings["battle"]["queueSize"].String();
if(queueSize == "auto")
embedQueue = screen->h < 700;
else
embedQueue = screen->h < 700 || queueSize == "small";
queue = std::make_shared<StackQueue>(embedQueue, *this);
if(!embedQueue)
{
if (settings["battle"]["showQueue"].Bool())
pos.y += queue->pos.h / 2; //center whole window
queue->moveTo(Point(pos.x, pos.y - queue->pos.h));
}
CPlayerInterface::battleInt = this;
//initializing armies
this->army1 = army1;
this->army2 = army2;
@ -108,84 +89,43 @@ BattleInterface::BattleInterface(const CCreatureSet *army1, const CCreatureSet *
if(town && town->hasFort())
siegeController.reset(new BattleSiegeController(*this, town));
controlPanel = std::make_shared<BattleControlPanel>(*this, Point(0, 556));
windowObject = std::make_shared<BattleWindow>(*this);
projectilesController.reset(new BattleProjectileController(*this));
fieldController.reset( new BattleFieldController(*this));
stacksController.reset( new BattleStacksController(*this));
actionsController.reset( new BattleActionsController(*this));
effectsController.reset(new BattleEffectsController(*this));
//loading hero animations
if(hero1) // attacking hero
{
std::string battleImage;
if(!hero1->type->battleImage.empty())
{
battleImage = hero1->type->battleImage;
}
else
{
if(hero1->sex)
battleImage = hero1->type->heroClass->imageBattleFemale;
else
battleImage = hero1->type->heroClass->imageBattleMale;
}
attackingHero = std::make_shared<BattleHero>(battleImage, false, hero1->tempOwner, hero1->tempOwner == curInt->playerID ? hero1 : nullptr, *this);
auto img = attackingHero->animation->getImage(0, 0, true);
if(img)
attackingHero->pos = genRect(img->height(), img->width(), pos.x - 43, pos.y - 19);
}
if(hero2) // defending hero
{
std::string battleImage;
if(!hero2->type->battleImage.empty())
{
battleImage = hero2->type->battleImage;
}
else
{
if(hero2->sex)
battleImage = hero2->type->heroClass->imageBattleFemale;
else
battleImage = hero2->type->heroClass->imageBattleMale;
}
defendingHero = std::make_shared<BattleHero>(battleImage, true, hero2->tempOwner, hero2->tempOwner == curInt->playerID ? hero2 : nullptr, *this);
auto img = defendingHero->animation->getImage(0, 0, true);
if(img)
defendingHero->pos = genRect(img->height(), img->width(), pos.x + 693, pos.y - 19);
}
obstacleController.reset(new BattleObstacleController(*this));
if(tacticsMode)
tacticNextStack(nullptr);
CCS->musich->stopMusic();
setAnimationCondition(EAnimationEvents::OPENING, true);
battleIntroSoundChannel = CCS->soundh->playSoundFromSet(CCS->soundh->battleIntroSounds);
auto onIntroPlayed = [&]()
auto onIntroPlayed = [this]()
{
if(LOCPLINT->battleInt)
{
CCS->musich->playMusicFromSet("battle", true, true);
battleActionsStarted = true;
activateStack();
controlPanel->blockUI(settings["session"]["spectate"].Bool() || stacksController->getActiveStack() == nullptr);
battleIntroSoundChannel = -1;
boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
onIntroSoundPlayed();
}
};
CCS->soundh->setCallback(battleIntroSoundChannel, onIntroPlayed);
GH.pushInt(windowObject);
windowObject->blockUI(true);
windowObject->updateQueue();
addUsedEvents(RCLICK | MOVE | KEYBOARD);
controlPanel->blockUI(true);
queue->update();
if (battleIntroSoundChannel != -1)
CCS->soundh->setCallback(battleIntroSoundChannel, onIntroPlayed);
else
onIntroSoundPlayed();
}
void BattleInterface::onIntroSoundPlayed()
{
setAnimationCondition(EAnimationEvents::OPENING, false);
CCS->musich->playMusicFromSet("battle", true, true);
if(tacticsMode)
tacticNextStack(nullptr);
activateStack();
battleIntroSoundChannel = -1;
}
BattleInterface::~BattleInterface()
@ -193,18 +133,17 @@ BattleInterface::~BattleInterface()
CPlayerInterface::battleInt = nullptr;
givenCommand.cond.notify_all(); //that two lines should make any stacksController->getActiveStack() waiting thread to finish
if (active) //dirty fix for #485
{
deactivate();
}
if (adventureInt && adventureInt->selection)
{
//FIXME: this should be moved to adventureInt which should restore correct track based on selection/active player
const auto & terrain = *(LOCPLINT->cb->getTile(adventureInt->selection->visitablePos())->terType);
CCS->musich->playMusicFromSet("terrain", terrain.name, true, false);
}
animsAreDisplayed.setn(false);
// may happen if user decided to close game while in battle
if (getAnimationCondition(EAnimationEvents::ACTION) == true)
logGlobal->error("Shutting down BattleInterface during animation playback!");
setAnimationCondition(EAnimationEvents::ACTION, false);
}
void BattleInterface::setPrintCellBorders(bool set)
@ -231,84 +170,6 @@ void BattleInterface::setPrintMouseShadow(bool set)
shadow->Bool() = set;
}
void BattleInterface::activate()
{
controlPanel->activate();
if (curInt->isAutoFightOn)
return;
CIntObject::activate();
if (attackingHero)
attackingHero->activate();
if (defendingHero)
defendingHero->activate();
fieldController->activate();
if (settings["battle"]["showQueue"].Bool())
queue->activate();
LOCPLINT->cingconsole->activate();
}
void BattleInterface::deactivate()
{
controlPanel->deactivate();
CIntObject::deactivate();
fieldController->deactivate();
if (attackingHero)
attackingHero->deactivate();
if (defendingHero)
defendingHero->deactivate();
if (settings["battle"]["showQueue"].Bool())
queue->deactivate();
LOCPLINT->cingconsole->deactivate();
}
void BattleInterface::keyPressed(const SDL_KeyboardEvent & key)
{
if(key.keysym.sym == SDLK_q && key.state == SDL_PRESSED)
{
if(settings["battle"]["showQueue"].Bool()) //hide queue
hideQueue();
else
showQueue();
}
else if(key.keysym.sym == SDLK_f && key.state == SDL_PRESSED)
{
actionsController->enterCreatureCastingMode();
}
else if(key.keysym.sym == SDLK_ESCAPE)
{
if(!battleActionsStarted)
CCS->soundh->stopSound(battleIntroSoundChannel);
else
actionsController->endCastingSpell();
}
}
void BattleInterface::mouseMoved(const SDL_MouseMotionEvent &event)
{
BattleHex selectedHex = fieldController->getHoveredHex();
actionsController->handleHex(selectedHex, MOVE);
controlPanel->mouseMoved(event);
}
void BattleInterface::clickRight(tribool down, bool previousState)
{
if (!down)
{
actionsController->endCastingSpell();
}
}
void BattleInterface::stackReset(const CStack * stack)
{
stacksController->stackReset(stack);
@ -316,24 +177,27 @@ void BattleInterface::stackReset(const CStack * stack)
void BattleInterface::stackAdded(const CStack * stack)
{
stacksController->stackAdded(stack);
stacksController->stackAdded(stack, false);
}
void BattleInterface::stackRemoved(uint32_t stackID)
{
stacksController->stackRemoved(stackID);
fieldController->redrawBackgroundWithHexes();
queue->update();
windowObject->updateQueue();
}
void BattleInterface::stackActivated(const CStack *stack) //TODO: check it all before game state is changed due to abilities
void BattleInterface::stackActivated(const CStack *stack)
{
stacksController->stackActivated(stack);
}
void BattleInterface::stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance)
void BattleInterface::stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance, bool teleport)
{
stacksController->stackMoved(stack, destHex, distance);
if (teleport)
stacksController->stackTeleported(stack, destHex, distance);
else
stacksController->stackMoved(stack, destHex, distance);
}
void BattleInterface::stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos)
@ -354,25 +218,25 @@ void BattleInterface::stacksAreAttacked(std::vector<StackAttackedInfo> attackedI
for(ui8 side = 0; side < 2; side++)
{
if(killedBySide.at(side) > killedBySide.at(1-side))
setHeroAnimation(side, CCreatureAnim::HERO_DEFEAT);
setHeroAnimation(side, EHeroAnimType::DEFEAT);
else if(killedBySide.at(side) < killedBySide.at(1-side))
setHeroAnimation(side, CCreatureAnim::HERO_VICTORY);
setHeroAnimation(side, EHeroAnimType::VICTORY);
}
}
void BattleInterface::stackAttacking( const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting )
void BattleInterface::stackAttacking( const StackAttackInfo & attackInfo )
{
stacksController->stackAttacking(attacker, dest, attacked, shooting);
stacksController->stackAttacking(attackInfo);
}
void BattleInterface::newRoundFirst( int round )
{
waitForAnims();
waitForAnimationCondition(EAnimationEvents::OPENING, false);
}
void BattleInterface::newRound(int number)
{
controlPanel->console->addText(CGI->generaltexth->allTexts[412]);
console->addText(CGI->generaltexth->allTexts[412]);
}
void BattleInterface::giveCommand(EActionType action, BattleHex tile, si32 additional)
@ -435,11 +299,6 @@ const CGHeroInstance * BattleInterface::getActiveHero()
return defendingHeroInstance;
}
void BattleInterface::hexLclicked(int whichOne)
{
actionsController->handleHex(whichOne, LCLICK);
}
void BattleInterface::stackIsCatapulting(const CatapultAttack & ca)
{
if (siegeController)
@ -454,34 +313,31 @@ void BattleInterface::gateStateChanged(const EGateState state)
void BattleInterface::battleFinished(const BattleResult& br)
{
bresult = &br;
{
auto unlockPim = vstd::makeUnlockGuard(*CPlayerInterface::pim);
animsAreDisplayed.waitUntil(false);
}
assert(getAnimationCondition(EAnimationEvents::ACTION) == false);
waitForAnimationCondition(EAnimationEvents::ACTION, false);
stacksController->setActiveStack(nullptr);
displayBattleFinished();
}
void BattleInterface::displayBattleFinished()
{
CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
CCS->curh->set(Cursor::Map::POINTER);
curInt->waitWhileDialog();
if(settings["session"]["spectate"].Bool() && settings["session"]["spectate-skip-battle-result"].Bool())
{
close();
windowObject->close();
return;
}
GH.pushInt(std::make_shared<BattleResultWindow>(*bresult, *(this->curInt)));
GH.pushInt(std::make_shared<BattleResultWindow>(br, *(this->curInt)));
curInt->waitWhileDialog(); // Avoid freeze when AI end turn after battle. Check bug #1897
CPlayerInterface::battleInt = nullptr;
}
void BattleInterface::spellCast(const BattleSpellCast * sc)
{
windowObject->blockUI(true);
const SpellID spellID = sc->spellID;
const CSpell * spell = spellID.toSpell();
auto targetedTile = sc->tile;
assert(spell);
if(!spell)
@ -490,7 +346,15 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
const std::string & castSoundPath = spell->getCastSound();
if (!castSoundPath.empty())
CCS->soundh->playSound(castSoundPath);
{
auto group = spell->animationInfo.projectile.empty() ?
EAnimationEvents::HIT:
EAnimationEvents::BEFORE_HIT;//FIXME: recheck whether this should be on projectile spawning
executeOnAnimationCondition(group, true, [=]() {
CCS->soundh->playSound(castSoundPath);
});
}
if ( sc->activeCast )
{
@ -498,56 +362,77 @@ void BattleInterface::spellCast(const BattleSpellCast * sc)
if(casterStack != nullptr )
{
displaySpellCast(spellID, casterStack->getPosition());
stacksController->addNewAnim(new CCastAnimation(*this, casterStack, sc->tile, curInt->cb->battleGetStackByPos(sc->tile), spell));
executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]()
{
stacksController->addNewAnim(new CastAnimation(*this, casterStack, targetedTile, curInt->cb->battleGetStackByPos(targetedTile), spell));
displaySpellCast(spell, casterStack->getPosition());
});
}
else
if (sc->tile.isValid() && !spell->animationInfo.projectile.empty())
{
// this is spell cast by hero with valid destination & valid projectile -> play animation
auto hero = sc->side ? defendingHero : attackingHero;
assert(hero);
const CStack * target = curInt->cb->battleGetStackByPos(sc->tile);
Point srccoord = (sc->side ? Point(770, 60) : Point(30, 60)) + pos; //hero position
Point destcoord = stacksController->getStackPositionAtHex(sc->tile, target); //position attacked by projectile
destcoord += Point(250, 240); // FIXME: what are these constants?
projectilesController->createSpellProjectile( nullptr, srccoord, destcoord, spell);
projectilesController->emitStackProjectile( nullptr );
// wait fo projectile to end
stacksController->addNewAnim(new CWaitingProjectileAnimation(*this, nullptr));
executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]()
{
stacksController->addNewAnim(new HeroCastAnimation(*this, hero, targetedTile, curInt->cb->battleGetStackByPos(targetedTile), spell));
});
}
}
waitForAnims(); //wait for projectile animation
displaySpellHit(spellID, sc->tile);
executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){
displaySpellHit(spell, targetedTile);
});
//queuing affect animation
for(auto & elem : sc->affectedCres)
{
auto stack = curInt->cb->battleGetStackByID(elem, false);
assert(stack);
if(stack)
displaySpellEffect(spellID, stack->getPosition());
{
executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){
displaySpellEffect(spell, stack->getPosition());
});
}
}
//queuing additional animation
for(auto & elem : sc->customEffects)
for(auto & elem : sc->reflectedCres)
{
auto stack = curInt->cb->battleGetStackByID(elem.stack, false);
if(stack)
effectsController->displayEffect(EBattleEffect::EBattleEffect(elem.effect), stack->getPosition());
auto stack = curInt->cb->battleGetStackByID(elem, false);
assert(stack);
executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){
effectsController->displayEffect(EBattleEffect::MAGIC_MIRROR, stack->getPosition());
});
}
if (!sc->resistedCres.empty())
{
executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){
CCS->soundh->playSound("MAGICRES");
});
}
for(auto & elem : sc->resistedCres)
{
auto stack = curInt->cb->battleGetStackByID(elem, false);
assert(stack);
executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){
effectsController->displayEffect(EBattleEffect::RESISTANCE, stack->getPosition());
});
}
waitForAnims();
//mana absorption
if (sc->manaGained > 0)
{
Point leftHero = Point(15, 30) + pos;
Point rightHero = Point(755, 30) + pos;
stacksController->addNewAnim(new CPointEffectAnimation(*this, soundBase::invalid, sc->side ? "SP07_A.DEF" : "SP07_B.DEF", leftHero));
stacksController->addNewAnim(new CPointEffectAnimation(*this, soundBase::invalid, sc->side ? "SP07_B.DEF" : "SP07_A.DEF", rightHero));
Point leftHero = Point(15, 30);
Point rightHero = Point(755, 30);
bool side = sc->side;
executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=](){
stacksController->addNewAnim(new EffectAnimation(*this, side ? "SP07_A.DEF" : "SP07_B.DEF", leftHero));
stacksController->addNewAnim(new EffectAnimation(*this, side ? "SP07_B.DEF" : "SP07_A.DEF", rightHero));
});
}
}
@ -557,7 +442,7 @@ void BattleInterface::battleStacksEffectsSet(const SetStackEffect & sse)
fieldController->redrawBackgroundWithHexes();
}
void BattleInterface::setHeroAnimation(ui8 side, int phase)
void BattleInterface::setHeroAnimation(ui8 side, EHeroAnimType phase)
{
if(side == BattleSide::ATTACKER)
{
@ -577,60 +462,62 @@ void BattleInterface::displayBattleLog(const std::vector<MetaString> & battleLog
{
std::string formatted = line.toString();
boost::algorithm::trim(formatted);
if(!controlPanel->console->addText(formatted))
logGlobal->warn("Too long battle log line");
appendBattleLog(formatted);
}
}
void BattleInterface::displaySpellAnimationQueue(const CSpell::TAnimationQueue & q, BattleHex destinationTile, bool isHit)
void BattleInterface::displaySpellAnimationQueue(const CSpell * spell, const CSpell::TAnimationQueue & q, BattleHex destinationTile, bool isHit)
{
for(const CSpell::TAnimation & animation : q)
{
if(animation.pause > 0)
stacksController->addNewAnim(new CDummyAnimation(*this, animation.pause));
else
stacksController->addNewAnim(new DummyAnimation(*this, animation.pause));
if (!animation.effectName.empty())
{
const CStack * destStack = getCurrentPlayerInterface()->cb->battleGetStackByPos(destinationTile, false);
if (destStack)
stacksController->addNewAnim(new ColorTransformAnimation(*this, destStack, animation.effectName, spell ));
}
if(!animation.resourceName.empty())
{
int flags = 0;
if (isHit)
flags |= CPointEffectAnimation::FORCE_ON_TOP;
flags |= EffectAnimation::FORCE_ON_TOP;
if (animation.verticalPosition == VerticalPosition::BOTTOM)
flags |= CPointEffectAnimation::ALIGN_TO_BOTTOM;
flags |= EffectAnimation::ALIGN_TO_BOTTOM;
if (!destinationTile.isValid())
flags |= CPointEffectAnimation::SCREEN_FILL;
flags |= EffectAnimation::SCREEN_FILL;
if (!destinationTile.isValid())
stacksController->addNewAnim(new CPointEffectAnimation(*this, soundBase::invalid, animation.resourceName, flags));
stacksController->addNewAnim(new EffectAnimation(*this, animation.resourceName, flags));
else
stacksController->addNewAnim(new CPointEffectAnimation(*this, soundBase::invalid, animation.resourceName, destinationTile, flags));
stacksController->addNewAnim(new EffectAnimation(*this, animation.resourceName, destinationTile, flags));
}
}
}
void BattleInterface::displaySpellCast(SpellID spellID, BattleHex destinationTile)
void BattleInterface::displaySpellCast(const CSpell * spell, BattleHex destinationTile)
{
const CSpell * spell = spellID.toSpell();
if(spell)
displaySpellAnimationQueue(spell->animationInfo.cast, destinationTile, false);
displaySpellAnimationQueue(spell, spell->animationInfo.cast, destinationTile, false);
}
void BattleInterface::displaySpellEffect(SpellID spellID, BattleHex destinationTile)
void BattleInterface::displaySpellEffect(const CSpell * spell, BattleHex destinationTile)
{
const CSpell *spell = spellID.toSpell();
if(spell)
displaySpellAnimationQueue(spell->animationInfo.affect, destinationTile, false);
displaySpellAnimationQueue(spell, spell->animationInfo.affect, destinationTile, false);
}
void BattleInterface::displaySpellHit(SpellID spellID, BattleHex destinationTile)
void BattleInterface::displaySpellHit(const CSpell * spell, BattleHex destinationTile)
{
const CSpell * spell = spellID.toSpell();
if(spell)
displaySpellAnimationQueue(spell->animationInfo.hit, destinationTile, true);
displaySpellAnimationQueue(spell, spell->animationInfo.hit, destinationTile, true);
}
void BattleInterface::setAnimSpeed(int set)
@ -663,9 +550,6 @@ void BattleInterface::trySetActivePlayer( PlayerColor player )
void BattleInterface::activateStack()
{
if(!battleActionsStarted)
return; //"show" function should re-call this function
stacksController->activateStack();
const CStack * s = stacksController->getActiveStack();
@ -673,7 +557,8 @@ void BattleInterface::activateStack()
return;
myTurn = true;
queue->update();
windowObject->updateQueue();
windowObject->blockUI(false);
fieldController->redrawBackgroundWithHexes();
actionsController->activateStack();
GH.fakeMouseMove();
@ -683,66 +568,28 @@ void BattleInterface::endAction(const BattleAction* action)
{
const CStack *stack = curInt->cb->battleGetStackByID(action->stackNumber);
if(action->actionType == EActionType::HERO_SPELL)
setHeroAnimation(action->side, CCreatureAnim::HERO_HOLDING);
stacksController->endAction(action);
windowObject->updateQueue();
queue->update();
if (tacticsMode) //stack ended movement in tactics phase -> select the next one
//stack ended movement in tactics phase -> select the next one
if (tacticsMode)
tacticNextStack(stack);
if(action->actionType == EActionType::HERO_SPELL) //we have activated next stack after sending request that has been just realized -> blockmap due to movement has changed
//we have activated next stack after sending request that has been just realized -> blockmap due to movement has changed
if(action->actionType == EActionType::HERO_SPELL)
fieldController->redrawBackgroundWithHexes();
// if (stacksController->getActiveStack() && !animsAreDisplayed.get() && pendingAnims.empty() && !active)
// {
// logGlobal->warn("Something wrong... interface was deactivated but there is no animation. Reactivating...");
// controlPanel->blockUI(false);
// }
// else
// {
// block UI if no active stack (e.g. enemy turn);
controlPanel->blockUI(stacksController->getActiveStack() == nullptr);
// }
}
void BattleInterface::hideQueue()
void BattleInterface::appendBattleLog(const std::string & newEntry)
{
Settings showQueue = settings.write["battle"]["showQueue"];
showQueue->Bool() = false;
queue->deactivate();
if (!queue->embedded)
{
moveBy(Point(0, -queue->pos.h / 2));
GH.totalRedraw();
}
}
void BattleInterface::showQueue()
{
Settings showQueue = settings.write["battle"]["showQueue"];
showQueue->Bool() = true;
queue->activate();
if (!queue->embedded)
{
moveBy(Point(0, +queue->pos.h / 2));
GH.totalRedraw();
}
console->addText(newEntry);
}
void BattleInterface::startAction(const BattleAction* action)
{
controlPanel->blockUI(true);
if(action->actionType == EActionType::END_TACTIC_PHASE)
{
controlPanel->tacticPhaseEnded();
windowObject->tacticPhaseEnded();
return;
}
@ -750,7 +597,7 @@ void BattleInterface::startAction(const BattleAction* action)
if (stack)
{
queue->update();
windowObject->updateQueue();
}
else
{
@ -759,13 +606,8 @@ void BattleInterface::startAction(const BattleAction* action)
stacksController->startAction(action);
redraw(); // redraw after deactivation, including proper handling of hovered hexes
if(action->actionType == EActionType::HERO_SPELL) //when hero casts spell
{
setHeroAnimation(action->side, CCreatureAnim::HERO_CAST_SPELL);
return;
}
if (!stack)
{
@ -776,16 +618,9 @@ void BattleInterface::startAction(const BattleAction* action)
effectsController->startAction(action);
}
void BattleInterface::waitForAnims()
{
auto unlockPim = vstd::makeUnlockGuard(*CPlayerInterface::pim);
animsAreDisplayed.waitWhileTrue();
}
void BattleInterface::tacticPhaseEnd()
{
stacksController->setActiveStack(nullptr);
controlPanel->blockUI(true);
tacticsMode = false;
}
@ -800,7 +635,8 @@ void BattleInterface::tacticNextStack(const CStack * current)
current = stacksController->getActiveStack();
//no switching stacks when the current one is moving
waitForAnims();
assert(getAnimationCondition(EAnimationEvents::ACTION) == false);
waitForAnimationCondition(EAnimationEvents::ACTION, false);
TStacks stacksOfMine = tacticianInterface->cb->battleGetStacks(CBattleCallback::ONLY_MINE);
vstd::erase_if (stacksOfMine, &immobile);
@ -867,7 +703,6 @@ void BattleInterface::requestAutofightingAIToTakeAction()
//TODO implement the possibility that the AI will be triggered for further actions
//TODO any solution to merge tactics phase & normal phase in the way it is handled by the player and battle interface?
stacksController->setActiveStack(nullptr);
controlPanel->blockUI(true);
tacticsMode = false;
}
else
@ -885,81 +720,49 @@ void BattleInterface::requestAutofightingAIToTakeAction()
aiThread.detach();
}
void BattleInterface::showAll(SDL_Surface *to)
{
show(to);
}
void BattleInterface::show(SDL_Surface *to)
{
Canvas canvas(to);
assert(to);
SDL_Rect buf;
SDL_GetClipRect(to, &buf);
SDL_SetClipRect(to, &pos);
++animCount;
fieldController->renderBattlefield(canvas);
if(battleActionsStarted)
stacksController->updateBattleAnimations();
SDL_SetClipRect(to, &buf); //restoring previous clip_rect
showInterface(to);
//activation of next stack, if any
//TODO: should be moved to the very start of this method?
//activateStack();
}
void BattleInterface::collectRenderableObjects(BattleRenderer & renderer)
{
if (attackingHero)
{
renderer.insert(EBattleFieldLayer::HEROES, BattleHex(0),[this](BattleRenderer::RendererRef canvas)
{
attackingHero->render(canvas);
});
}
if (defendingHero)
{
renderer.insert(EBattleFieldLayer::HEROES, BattleHex(GameConstants::BFIELD_WIDTH-1),[this](BattleRenderer::RendererRef canvas)
{
defendingHero->render(canvas);
});
}
}
void BattleInterface::showInterface(SDL_Surface * to)
{
//showing in-game console
LOCPLINT->cingconsole->show(to);
controlPanel->showAll(to);
Rect posWithQueue = Rect(pos.x, pos.y, 800, 600);
if (settings["battle"]["showQueue"].Bool())
{
if (!queue->embedded)
{
posWithQueue.y -= queue->pos.h;
posWithQueue.h += queue->pos.h;
}
queue->showAll(to);
}
//printing border around interface
if (screen->w != 800 || screen->h !=600)
{
CMessage::drawBorder(curInt->playerID,to,posWithQueue.w + 28, posWithQueue.h + 28, posWithQueue.x-14, posWithQueue.y-15);
}
}
void BattleInterface::castThisSpell(SpellID spellID)
{
actionsController->castThisSpell(spellID);
}
void BattleInterface::setAnimationCondition( EAnimationEvents event, bool state)
{
logAnim->debug("setAnimationCondition: %d -> %s", static_cast<int>(event), state ? "ON" : "OFF");
size_t index = static_cast<size_t>(event);
animationEvents[index].setn(state);
decltype(awaitingEvents) executingEvents;
for (auto it = awaitingEvents.begin(); it != awaitingEvents.end();)
{
if (it->event == event && it->eventState == state)
{
executingEvents.push_back(*it);
it = awaitingEvents.erase(it);
}
else
++it;
}
for (auto const & event : executingEvents)
event.action();
}
bool BattleInterface::getAnimationCondition( EAnimationEvents event)
{
size_t index = static_cast<size_t>(event);
return animationEvents[index].get();
}
void BattleInterface::waitForAnimationCondition( EAnimationEvents event, bool state)
{
auto unlockPim = vstd::makeUnlockGuard(*CPlayerInterface::pim);
size_t index = static_cast<size_t>(event);
animationEvents[index].waitUntil(state);
}
void BattleInterface::executeOnAnimationCondition( EAnimationEvents event, bool state, const AwaitingAnimationAction & action)
{
awaitingEvents.push_back({action, event, state});
}

View File

@ -9,8 +9,10 @@
*/
#pragma once
#include "BattleConstants.h"
#include "../gui/CIntObject.h"
#include "../../lib/spells/CSpellHandler.h" //CSpell::TAnimation
#include "../../lib/CondSh.h"
VCMI_LIB_NAMESPACE_BEGIN
@ -20,7 +22,6 @@ class CStack;
struct BattleResult;
struct BattleSpellCast;
struct CObstacleInstance;
template <typename T> struct CondSh;
struct SetStackEffect;
class BattleAction;
class CGTownInstance;
@ -28,7 +29,6 @@ struct CatapultAttack;
struct BattleTriggerEffect;
struct BattleHex;
struct InfoAboutHero;
struct CustomEffectInfo;
VCMI_LIB_NAMESPACE_END
@ -48,46 +48,109 @@ class BattleSiegeController;
class BattleObstacleController;
class BattleFieldController;
class BattleRenderer;
class BattleControlPanel;
class BattleWindow;
class BattleStacksController;
class BattleActionsController;
class BattleEffectsController;
class BattleConsole;
/// Small struct which contains information about the id of the attacked stack, the damage dealt,...
struct StackAttackedInfo
{
const CStack *defender; //attacked stack
int64_t dmg; //damage dealt
unsigned int amountKilled; //how many creatures in stack has been killed
const CStack *attacker; //attacking stack
const CStack *defender;
const CStack *attacker;
int64_t damageDealt;
uint32_t amountKilled;
SpellID spellEffect;
bool indirectAttack; //if true, stack was attacked indirectly - spell or ranged attack
bool killed; //if true, stack has been killed
bool rebirth; //if true, play rebirth animation after all
bool cloneKilled;
bool fireShield;
};
/// Big class which handles the overall battle interface actions and it is also responsible for
/// drawing everything correctly.
class BattleInterface : public WindowBase
struct StackAttackInfo
{
private:
std::shared_ptr<BattleHero> attackingHero;
std::shared_ptr<BattleHero> defendingHero;
std::shared_ptr<StackQueue> queue;
std::shared_ptr<BattleControlPanel> controlPanel;
const CStack *attacker;
const CStack *defender;
std::vector< const CStack *> secondaryDefender;
std::shared_ptr<CPlayerInterface> tacticianInterface; //used during tactics mode, points to the interface of player with higher tactics (can be either attacker or defender in hot-seat), valid onloy for human players
std::shared_ptr<CPlayerInterface> attackerInt, defenderInt; //because LOCPLINT is not enough in hotSeat
std::shared_ptr<CPlayerInterface> curInt; //current player interface
SpellID spellEffect;
BattleHex tile;
const CCreatureSet *army1, *army2; //copy of initial armies (for result window)
const CGHeroInstance *attackingHeroInstance, *defendingHeroInstance;
bool indirectAttack;
bool lucky;
bool unlucky;
bool deathBlow;
bool lifeDrain;
};
ui8 animCount;
/// Main class for battles, responsible for relaying information from server to various battle entities
class BattleInterface
{
using AwaitingAnimationAction = std::function<void()>;
struct AwaitingAnimationEvents {
AwaitingAnimationAction action;
EAnimationEvents event;
bool eventState;
};
/// Conditional variables that are set depending on ongoing animations on the battlefield
std::array< CondSh<bool>, static_cast<size_t>(EAnimationEvents::COUNT)> animationEvents;
/// List of events that are waiting to be triggered
std::vector<AwaitingAnimationEvents> awaitingEvents;
/// used during tactics mode, points to the interface of player with higher tactics (can be either attacker or defender in hot-seat), valid onloy for human players
std::shared_ptr<CPlayerInterface> tacticianInterface;
/// attacker interface, not null if attacker is human in our vcmiclient
std::shared_ptr<CPlayerInterface> attackerInt;
/// defender interface, not null if attacker is human in our vcmiclient
std::shared_ptr<CPlayerInterface> defenderInt;
void onIntroSoundPlayed();
public:
/// copy of initial armies (for result window)
const CCreatureSet *army1;
const CCreatureSet *army2;
/// ID of channel on which battle opening sound is playing, or -1 if none
int battleIntroSoundChannel;
std::shared_ptr<BattleWindow> windowObject;
std::shared_ptr<BattleConsole> console;
/// currently active player interface
std::shared_ptr<CPlayerInterface> curInt;
const CGHeroInstance *attackingHeroInstance;
const CGHeroInstance *defendingHeroInstance;
bool tacticsMode;
bool battleActionsStarted; //used for delaying battle actions until intro sound stops
int battleIntroSoundChannel; //required as variable for disabling it via ESC key
std::unique_ptr<BattleProjectileController> projectilesController;
std::unique_ptr<BattleSiegeController> siegeController;
std::unique_ptr<BattleObstacleController> obstacleController;
std::unique_ptr<BattleFieldController> fieldController;
std::unique_ptr<BattleStacksController> stacksController;
std::unique_ptr<BattleActionsController> actionsController;
std::unique_ptr<BattleEffectsController> effectsController;
std::shared_ptr<BattleHero> attackingHero;
std::shared_ptr<BattleHero> defendingHero;
static CondSh<BattleAction *> givenCommand; //data != nullptr if we have i.e. moved current unit
bool myTurn; //if true, interface is active (commands can be ordered)
int moveSoundHander; // sound handler used when moving a unit
BattleInterface(const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt = nullptr);
~BattleInterface();
void trySetActivePlayer( PlayerColor player ); // if in hotseat, will activate interface of chosen player
void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all
@ -100,27 +163,11 @@ private:
void showInterface(SDL_Surface * to);
void setHeroAnimation(ui8 side, int phase);
public:
std::unique_ptr<BattleProjectileController> projectilesController;
std::unique_ptr<BattleSiegeController> siegeController;
std::unique_ptr<BattleObstacleController> obstacleController;
std::unique_ptr<BattleFieldController> fieldController;
std::unique_ptr<BattleStacksController> stacksController;
std::unique_ptr<BattleActionsController> actionsController;
std::unique_ptr<BattleEffectsController> effectsController;
void setHeroAnimation(ui8 side, EHeroAnimType phase);
static CondSh<bool> animsAreDisplayed; //for waiting with the end of battle for end of anims
static CondSh<BattleAction *> givenCommand; //data != nullptr if we have i.e. moved current unit
void executeSpellCast(); //called when a hero casts a spell
bool myTurn; //if true, interface is active (commands can be ordered)
bool moveStarted; //if true, the creature that is already moving is going to make its first step
int moveSoundHander; // sound handler used when moving a unit
const BattleResult *bresult; //result of a battle; if non-zero then display when all animations end
BattleInterface(const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const SDL_Rect & myRect, std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt = nullptr);
virtual ~BattleInterface();
void appendBattleLog(const std::string & newEntry);
void setPrintCellBorders(bool set); //if true, cell borders will be printed
void setPrintStackRange(bool set); //if true,range of active stack will be printed
@ -131,19 +178,18 @@ public:
void tacticNextStack(const CStack *current);
void tacticPhaseEnd();
void waitForAnims();
//napisz tu klase odpowiadajaca za wyswietlanie bitwy i obsluge uzytkownika, polecenia ma przekazywac callbackiem
void activate() override;
void deactivate() override;
void keyPressed(const SDL_KeyboardEvent & key) override;
void mouseMoved(const SDL_MouseMotionEvent &sEvent) override;
void clickRight(tribool down, bool previousState) override;
/// sets condition to targeted state and executes any awaiting actions
void setAnimationCondition( EAnimationEvents event, bool state);
void show(SDL_Surface *to) override;
void showAll(SDL_Surface *to) override;
/// returns current state of condition
bool getAnimationCondition( EAnimationEvents event);
void collectRenderableObjects(BattleRenderer & renderer);
/// locks execution until selected condition reached targeted state
void waitForAnimationCondition( EAnimationEvents event, bool state);
/// adds action that will be executed one selected condition reached targeted state
void executeOnAnimationCondition( EAnimationEvents event, bool state, const AwaitingAnimationAction & action);
//call-ins
void startAction(const BattleAction* action);
@ -151,29 +197,25 @@ public:
void stackAdded(const CStack * stack); //new stack appeared on battlefield
void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled
void stackActivated(const CStack *stack); //active stack has been changed
void stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance); //stack with id number moved to destHex
void stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance, bool teleport); //stack with id number moved to destHex
void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked
void stackAttacking(const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting); //called when stack with id ID is attacking something on hex dest
void stackAttacking(const StackAttackInfo & attackInfo); //called when stack with id ID is attacking something on hex dest
void newRoundFirst( int round );
void newRound(int number); //caled when round is ended; number is the number of round
void hexLclicked(int whichOne); //hex only call-in
void stackIsCatapulting(const CatapultAttack & ca); //called when a stack is attacking walls
void battleFinished(const BattleResult& br); //called when battle is finished - battleresult window should be printed
void displayBattleFinished(); //displays battle result
void spellCast(const BattleSpellCast *sc); //called when a hero casts a spell
void battleStacksEffectsSet(const SetStackEffect & sse); //called when a specific effect is set to stacks
void castThisSpell(SpellID spellID); //called when player has chosen a spell from spellbook
void displayBattleLog(const std::vector<MetaString> & battleLog);
void displaySpellAnimationQueue(const CSpell::TAnimationQueue & q, BattleHex destinationTile, bool isHit);
void displaySpellCast(SpellID spellID, BattleHex destinationTile); //displays spell`s cast animation
void displaySpellEffect(SpellID spellID, BattleHex destinationTile); //displays spell`s affected animation
void displaySpellHit(SpellID spellID, BattleHex destinationTile); //displays spell`s affected animation
void displaySpellAnimationQueue(const CSpell * spell, const CSpell::TAnimationQueue & q, BattleHex destinationTile, bool isHit);
void displaySpellCast(const CSpell * spell, BattleHex destinationTile); //displays spell`s cast animation
void displaySpellEffect(const CSpell * spell, BattleHex destinationTile); //displays spell`s affected animation
void displaySpellHit(const CSpell * spell, BattleHex destinationTile); //displays spell`s affected animation
void endAction(const BattleAction* action);
void hideQueue();
void showQueue();
void obstaclePlaced(const std::vector<std::shared_ptr<const CObstacleInstance>> oi);
@ -181,29 +223,4 @@ public:
const CGHeroInstance *currentHero() const;
InfoAboutHero enemyHero() const;
// TODO: cleanup this list
friend class CPlayerInterface;
friend class CInGameConsole;
friend class StackQueue;
friend class BattleResultWindow;
friend class BattleHero;
friend class CBattleStackAnimation;
friend class CReverseAnimation;
friend class CDefenceAnimation;
friend class CMovementAnimation;
friend class CMovementStartAnimation;
friend class CAttackAnimation;
friend class CMeleeAttackAnimation;
friend class CShootingAnimation;
friend class CCastAnimation;
friend class ClickableHex;
friend class BattleProjectileController;
friend class BattleSiegeController;
friend class BattleObstacleController;
friend class BattleFieldController;
friend class BattleControlPanel;
friend class BattleStacksController;
friend class BattleActionsController;
friend class BattleEffectsController;
};

View File

@ -12,10 +12,11 @@
#include "BattleInterface.h"
#include "BattleActionsController.h"
#include "BattleRenderer.h"
#include "BattleSiegeController.h"
#include "BattleFieldController.h"
#include "BattleStacksController.h"
#include "BattleControlPanel.h"
#include "BattleWindow.h"
#include "../CGameInfo.h"
#include "../CMessage.h"
@ -41,6 +42,7 @@
#include "../../lib/CGameState.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/CTownHandler.h"
#include "../../lib/CHeroHandler.h"
#include "../../lib/NetPacks.h"
#include "../../lib/StartInfo.h"
#include "../../lib/CondSh.h"
@ -48,70 +50,99 @@
void BattleConsole::showAll(SDL_Surface * to)
{
Point consolePos(pos.x + 10, pos.y + 17);
Point textPos (pos.x + pos.w/2, pos.y + 17);
CIntObject::showAll(to);
if (!consoleText.empty())
Point line1 (pos.x + pos.w/2, pos.y + 8);
Point line2 (pos.x + pos.w/2, pos.y + 24);
auto visibleText = getVisibleText();
if(visibleText.size() > 0)
graphics->fonts[FONT_SMALL]->renderTextCenter(to, visibleText[0], Colors::WHITE, line1);
if(visibleText.size() > 1)
graphics->fonts[FONT_SMALL]->renderTextCenter(to, visibleText[1], Colors::WHITE, line2);
}
std::vector<std::string> BattleConsole::getVisibleText()
{
// high priority texts that hide battle log entries
for (auto const & text : {consoleText, hoverText} )
{
graphics->fonts[FONT_SMALL]->renderTextLinesLeft(to, CMessage::breakText(consoleText, pos.w, FONT_SMALL), Colors::WHITE, consolePos);
if (text.empty())
continue;
auto result = CMessage::breakText(text, pos.w, FONT_SMALL);
if(result.size() > 2)
result.resize(2);
return result;
}
else if(!hoverText.empty())
// log is small enough to fit entirely - display it as such
if (logEntries.size() < 3)
return logEntries;
return { logEntries[scrollPosition - 1], logEntries[scrollPosition] };
}
std::vector<std::string> BattleConsole::splitText(const std::string &text)
{
std::vector<std::string> lines;
std::vector<std::string> output;
boost::split(lines, text, boost::is_any_of("\n"));
for (auto const & line : lines)
{
graphics->fonts[FONT_SMALL]->renderTextLinesCenter(to, CMessage::breakText(hoverText, pos.w, FONT_SMALL), Colors::WHITE, textPos);
}
else if(logEntries.size())
{
if(logEntries.size()==1)
if (graphics->fonts[FONT_SMALL]->getStringWidth(text) < pos.w)
{
graphics->fonts[FONT_SMALL]->renderTextLinesCenter(to, CMessage::breakText(logEntries[0], pos.w, FONT_SMALL), Colors::WHITE, textPos);
output.push_back(line);
}
else
{
graphics->fonts[FONT_SMALL]->renderTextLinesCenter(to, CMessage::breakText(logEntries[scrollPosition - 1], pos.w, FONT_SMALL), Colors::WHITE, textPos);
textPos.y += 16;
graphics->fonts[FONT_SMALL]->renderTextLinesCenter(to, CMessage::breakText(logEntries[scrollPosition], pos.w, FONT_SMALL), Colors::WHITE, textPos);
std::vector<std::string> substrings = CMessage::breakText(line, pos.w, FONT_SMALL);
output.insert(output.end(), substrings.begin(), substrings.end());
}
}
return output;
}
bool BattleConsole::addText(const std::string & text)
{
logGlobal->trace("CBattleConsole message: %s", text);
if(text.size()>70)
return false; //text too long!
int firstInToken = 0;
for(size_t i = 0; i < text.size(); ++i) //tokenize
{
if(text[i] == 10)
{
logEntries.push_back( text.substr(firstInToken, i-firstInToken) );
firstInToken = (int)i+1;
}
}
logEntries.push_back( text.substr(firstInToken, text.size()) );
auto newLines = splitText(text);
logEntries.insert(logEntries.end(), newLines.begin(), newLines.end());
scrollPosition = (int)logEntries.size()-1;
redraw();
return true;
}
void BattleConsole::scrollUp(ui32 by)
{
if(scrollPosition > static_cast<int>(by))
scrollPosition -= by;
redraw();
}
void BattleConsole::scrollDown(ui32 by)
{
if(scrollPosition + by < logEntries.size())
scrollPosition += by;
redraw();
}
BattleConsole::BattleConsole(const Rect & position)
BattleConsole::BattleConsole(std::shared_ptr<CPicture> backgroundSource, const Point & objectPos, const Point & imagePos, const Point &size)
: scrollPosition(-1)
, enteringText(false)
{
pos += position.topLeft();
pos.w = position.w;
pos.h = position.h;
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
pos += objectPos;
pos.w = size.x;
pos.h = size.y;
background = std::make_shared<CPicture>(backgroundSource->getSurface(), Rect(imagePos, size), 0, 0 );
}
void BattleConsole::deactivate()
@ -137,17 +168,20 @@ void BattleConsole::setEnteringMode(bool on)
CSDL_Ext::stopTextInput();
}
enteringText = on;
redraw();
}
void BattleConsole::setEnteredText(const std::string & text)
{
assert(enteringText == true);
consoleText = text;
redraw();
}
void BattleConsole::write(const std::string & Text)
{
hoverText = Text;
redraw();
}
void BattleConsole::clearIfMatching(const std::string & Text)
@ -161,75 +195,110 @@ void BattleConsole::clear()
write({});
}
const CGHeroInstance * BattleHero::instance()
{
return hero;
}
void BattleHero::render(Canvas & canvas)
{
auto flagFrame = flagAnimation->getImage(flagAnim, 0, true);
size_t groupIndex = static_cast<size_t>(phase);
if(!flagFrame)
return;
auto flagFrame = flagAnimation->getImage(flagCurrentFrame, 0, true);
auto heroFrame = animation->getImage(currentFrame, groupIndex, true);
//animation of flag
Point flagPosition = pos.topLeft();
Point heroPosition = pos.center() - parent->pos.topLeft() - heroFrame->dimensions() / 2;
Point flagPosition = pos.center() - parent->pos.topLeft() - flagFrame->dimensions() / 2;
if(flip)
flagPosition += Point(61, 39);
if(defender)
flagPosition += Point(-4, -41);
else
flagPosition += Point(72, 39);
auto heroFrame = animation->getImage(currentFrame, phase, true);
flagPosition += Point(4, -41);
canvas.draw(flagFrame, flagPosition);
canvas.draw(heroFrame, pos.topLeft());
canvas.draw(heroFrame, heroPosition);
if(++animCount >= 4)
flagCurrentFrame += currentSpeed;
currentFrame += currentSpeed;
if(flagCurrentFrame >= flagAnimation->size(0))
flagCurrentFrame -= flagAnimation->size(0);
if(currentFrame >= animation->size(groupIndex))
{
animCount = 0;
if(++flagAnim >= flagAnimation->size(0))
flagAnim = 0;
if(++currentFrame >= lastFrame)
switchToNextPhase();
currentFrame -= animation->size(groupIndex);
switchToNextPhase();
}
}
void BattleHero::setPhase(int newPhase)
void BattleHero::pause()
{
currentSpeed = 0.f;
}
void BattleHero::play()
{
//FIXME: un-hardcode speed
currentSpeed = 0.25f;
}
float BattleHero::getFrame() const
{
return currentFrame;
}
void BattleHero::collectRenderableObjects(BattleRenderer & renderer)
{
auto hex = defender ? BattleHex(GameConstants::BFIELD_WIDTH-1) : BattleHex(0);
renderer.insert(EBattleFieldLayer::HEROES, hex, [this](BattleRenderer::RendererRef canvas)
{
render(canvas);
});
}
void BattleHero::onPhaseFinished(const std::function<void()> & callback)
{
phaseFinishedCallback = callback;
}
void BattleHero::setPhase(EHeroAnimType newPhase)
{
nextPhase = newPhase;
switchToNextPhase(); //immediately switch to next phase and then restore idling phase
nextPhase = 0;
nextPhase = EHeroAnimType::HOLDING;
}
void BattleHero::hover(bool on)
{
//TODO: Make lines below work properly
//TODO: BROKEN CODE
if (on)
CCS->curh->changeGraphic(ECursor::COMBAT, 5);
CCS->curh->set(Cursor::Combat::HERO);
else
CCS->curh->changeGraphic(ECursor::COMBAT, 0);
CCS->curh->set(Cursor::Combat::POINTER);
}
void BattleHero::clickLeft(tribool down, bool previousState)
{
if(myOwner->actionsController->spellcastingModeActive()) //we are casting a spell
if(owner.actionsController->spellcastingModeActive()) //we are casting a spell
return;
if(boost::logic::indeterminate(down))
return;
if(!myHero || down || !myOwner->myTurn)
if(!hero || down || !owner.myTurn)
return;
if(myOwner->getCurrentPlayerInterface()->cb->battleCanCastSpell(myHero, spells::Mode::HERO) == ESpellCastProblem::OK) //check conditions
if(owner.getCurrentPlayerInterface()->cb->battleCanCastSpell(hero, spells::Mode::HERO) == ESpellCastProblem::OK) //check conditions
{
BattleHex hoveredHex = myOwner->fieldController->getHoveredHex();
BattleHex hoveredHex = owner.fieldController->getHoveredHex();
//do nothing when any hex is hovered - hero's animation overlaps battlefield
if ( hoveredHex != BattleHex::INVALID )
return;
CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
CCS->curh->set(Cursor::Map::POINTER);
GH.pushIntT<CSpellWindow>(myHero, myOwner->getCurrentPlayerInterface());
GH.pushIntT<CSpellWindow>(hero, owner.getCurrentPlayerInterface());
}
}
@ -239,13 +308,13 @@ void BattleHero::clickRight(tribool down, bool previousState)
return;
Point windowPosition;
windowPosition.x = (!flip) ? myOwner->pos.topLeft().x + 1 : myOwner->pos.topRight().x - 79;
windowPosition.y = myOwner->pos.y + 135;
windowPosition.x = (!defender) ? owner.fieldController->pos.topLeft().x + 1 : owner.fieldController->pos.topRight().x - 79;
windowPosition.y = owner.fieldController->pos.y + 135;
InfoAboutHero targetHero;
if(down && (myOwner->myTurn || settings["session"]["spectate"].Bool()))
if(down && (owner.myTurn || settings["session"]["spectate"].Bool()))
{
auto h = flip ? myOwner->defendingHeroInstance : myOwner->attackingHeroInstance;
auto h = defender ? owner.defendingHeroInstance : owner.attackingHeroInstance;
targetHero.initFromHero(h, InfoAboutHero::EInfoLevel::INBATTLE);
GH.pushIntT<HeroInfoWindow>(targetHero, &windowPosition);
}
@ -253,43 +322,57 @@ void BattleHero::clickRight(tribool down, bool previousState)
void BattleHero::switchToNextPhase()
{
if(phase != nextPhase)
{
phase = nextPhase;
phase = nextPhase;
currentFrame = 0.f;
firstFrame = 0;
lastFrame = static_cast<int>(animation->size(phase));
}
currentFrame = firstFrame;
auto copy = phaseFinishedCallback;
phaseFinishedCallback.clear();
copy();
}
BattleHero::BattleHero(const std::string & animationPath, bool flipG, PlayerColor player, const CGHeroInstance * hero, const BattleInterface & owner):
flip(flipG),
myHero(hero),
myOwner(&owner),
phase(1),
nextPhase(0),
flagAnim(0),
animCount(0)
BattleHero::BattleHero(const BattleInterface & owner, const CGHeroInstance * hero, bool defender):
defender(defender),
hero(hero),
owner(owner),
phase(EHeroAnimType::HOLDING),
nextPhase(EHeroAnimType::HOLDING),
currentSpeed(0.f),
currentFrame(0.f),
flagCurrentFrame(0.f)
{
std::string animationPath;
if(!hero->type->battleImage.empty())
animationPath = hero->type->battleImage;
else
if(hero->sex)
animationPath = hero->type->heroClass->imageBattleFemale;
else
animationPath = hero->type->heroClass->imageBattleMale;
animation = std::make_shared<CAnimation>(animationPath);
animation->preload();
if(flipG)
pos.w = 64;
pos.h = 136;
pos.x = owner.fieldController->pos.x + (defender ? (owner.fieldController->pos.w - pos.w) : 0);
pos.y = owner.fieldController->pos.y;
if(defender)
animation->verticalFlip();
if(flip)
if(defender)
flagAnimation = std::make_shared<CAnimation>("CMFLAGR");
else
flagAnimation = std::make_shared<CAnimation>("CMFLAGL");
flagAnimation->preload();
flagAnimation->playerColored(player);
flagAnimation->playerColored(hero->tempOwner);
addUsedEvents(LCLICK | RCLICK | HOVER);
switchToNextPhase();
play();
}
HeroInfoWindow::HeroInfoWindow(const InfoAboutHero & hero, Point * position)
@ -584,7 +667,7 @@ void BattleResultWindow::bExitf()
close();
if(dynamic_cast<BattleInterface*>(GH.topInt().get()))
if(dynamic_cast<BattleWindow*>(GH.topInt().get()))
GH.popInts(1); //pop battle interface if present
//Result window and battle interface are gone. We requested all dialogs to be closed before opening the battle,
@ -599,7 +682,7 @@ void ClickableHex::hover(bool on)
//Hoverable::hover(on);
if(!on && setAlterText)
{
myInterface->controlPanel->console->clear();
GH.statusbar->clear();
setAlterText = false;
}
}
@ -623,13 +706,13 @@ void ClickableHex::mouseMoved(const SDL_MouseMotionEvent &sEvent)
MetaString text;
text.addTxt(MetaString::GENERAL_TXT, 220);
attackedStack->addNameReplacement(text);
myInterface->controlPanel->console->write(text.toString());
GH.statusbar->write(text.toString());
setAlterText = true;
}
}
else if(setAlterText)
{
myInterface->controlPanel->console->clear();
GH.statusbar->clear();
setAlterText = false;
}
}
@ -638,7 +721,7 @@ void ClickableHex::clickLeft(tribool down, bool previousState)
{
if(!down && hovered && strictHovered) //we've been really clicked!
{
myInterface->hexLclicked(myNumber);
myInterface->actionsController->handleHex(myNumber, LCLICK);
}
}
@ -662,10 +745,10 @@ StackQueue::StackQueue(bool Embedded, BattleInterface & owner)
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
if(embedded)
{
pos.w = QUEUE_SIZE * 37;
pos.h = 46;
pos.x = screen->w/2 - pos.w/2;
pos.y = (screen->h - 600)/2 + 10;
pos.w = QUEUE_SIZE * 41;
pos.h = 49;
pos.x += parent->pos.w/2 - pos.w/2;
pos.y += 10;
icons = std::make_shared<CAnimation>("CPRSMALL");
stateIcons = std::make_shared<CAnimation>("VCMI/BATTLEQUEUE/STATESSMALL");
@ -674,6 +757,8 @@ StackQueue::StackQueue(bool Embedded, BattleInterface & owner)
{
pos.w = 800;
pos.h = 85;
pos.x += 0;
pos.y -= pos.h;
background = std::make_shared<CFilledTexture>("DIBOXBCK", Rect(0, 0, pos.w, pos.h));
@ -688,10 +773,17 @@ StackQueue::StackQueue(bool Embedded, BattleInterface & owner)
for (int i = 0; i < stackBoxes.size(); i++)
{
stackBoxes[i] = std::make_shared<StackBox>(this);
stackBoxes[i]->moveBy(Point(1 + (embedded ? 36 : 80) * i, 0));
stackBoxes[i]->moveBy(Point(1 + (embedded ? 41 : 80) * i, 0));
}
}
void StackQueue::show(SDL_Surface * to)
{
if (embedded)
showAll(to);
CIntObject::show(to);
}
void StackQueue::update()
{
std::vector<battle::Units> queueData;

View File

@ -9,7 +9,9 @@
*/
#pragma once
#include "BattleConstants.h"
#include "../gui/CIntObject.h"
#include "../../lib/FunctionList.h"
#include "../../lib/battle/BattleHex.h"
#include "../windows/CWindowObject.h"
@ -38,11 +40,14 @@ class CLabel;
class CTextBox;
class CAnimImage;
class CPlayerInterface;
class BattleRenderer;
/// Class which shows the console at the bottom of the battle screen and manages the text of the console
class BattleConsole : public CIntObject, public IStatusBar
{
private:
std::shared_ptr<CPicture> background;
/// List of all texts added during battle, essentially - log of entire battle
std::vector< std::string > logEntries;
@ -57,8 +62,14 @@ private:
/// if true then we are currently entering console text
bool enteringText;
/// splits text into individual strings for battle log
std::vector<std::string> splitText(const std::string &text);
/// select line(s) that will be visible in UI
std::vector<std::string> getVisibleText();
public:
BattleConsole(const Rect & position);
BattleConsole(std::shared_ptr<CPicture> backgroundSource, const Point & objectPos, const Point & imagePos, const Point &size);
void showAll(SDL_Surface * to) override;
void deactivate() override;
@ -78,27 +89,43 @@ public:
/// Hero battle animation
class BattleHero : public CIntObject
{
void switchToNextPhase();
public:
bool flip; //false if it's attacking hero, true otherwise
bool defender;
CFunctionList<void()> phaseFinishedCallback;
std::shared_ptr<CAnimation> animation;
std::shared_ptr<CAnimation> flagAnimation;
const CGHeroInstance * myHero; //this animation's hero instance
const BattleInterface * myOwner; //battle interface to which this animation is assigned
int phase; //stage of animation
int nextPhase; //stage of animation to be set after current phase is fully displayed
int currentFrame, firstFrame, lastFrame; //frame of animation
const CGHeroInstance * hero; //this animation's hero instance
const BattleInterface & owner; //battle interface to which this animation is assigned
EHeroAnimType phase; //stage of animation
EHeroAnimType nextPhase; //stage of animation to be set after current phase is fully displayed
float currentSpeed;
float currentFrame; //frame of animation
float flagCurrentFrame;
void switchToNextPhase();
size_t flagAnim;
ui8 animCount; //for flag animation
void render(Canvas & canvas); //prints next frame of animation to to
void setPhase(int newPhase); //sets phase of hero animation
public:
const CGHeroInstance * instance();
void setPhase(EHeroAnimType newPhase); //sets phase of hero animation
void collectRenderableObjects(BattleRenderer & renderer);
float getFrame() const;
void onPhaseFinished(const std::function<void()> &);
void pause();
void play();
void hover(bool on) override;
void clickLeft(tribool down, bool previousState) override; //call-in
void clickRight(tribool down, bool previousState) override; //call-in
BattleHero(const std::string & animationPath, bool filpG, PlayerColor player, const CGHeroInstance * hero, const BattleInterface & owner);
BattleHero(const BattleInterface & owner, const CGHeroInstance * hero, bool defender);
};
class HeroInfoWindow : public CWindowObject
@ -193,4 +220,6 @@ public:
StackQueue(bool Embedded, BattleInterface & owner);
void update();
void show(SDL_Surface * to) override;
};

View File

@ -15,17 +15,22 @@
#include "BattleAnimationClasses.h"
#include "BattleStacksController.h"
#include "BattleRenderer.h"
#include "CreatureAnimation.h"
#include "../CMusicHandler.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../gui/CAnimation.h"
#include "../gui/Canvas.h"
#include "../gui/CGuiHandler.h"
#include "../../CCallback.h"
#include "../../lib/battle/CObstacleInstance.h"
#include "../../lib/ObstacleHandler.h"
BattleObstacleController::BattleObstacleController(BattleInterface & owner):
owner(owner)
owner(owner),
timePassed(0.f)
{
auto obst = owner.curInt->cb->battleGetAllObstacles();
for(auto & elem : obst)
@ -72,10 +77,6 @@ void BattleObstacleController::loadObstacleImage(const CObstacleInstance & oi)
void BattleObstacleController::obstaclePlaced(const std::vector<std::shared_ptr<const CObstacleInstance>> & obstacles)
{
assert(obstaclesBeingPlaced.empty());
for (auto const & oi : obstacles)
obstaclesBeingPlaced.push_back(oi->uniqueID);
for (auto const & oi : obstacles)
{
auto spellObstacle = dynamic_cast<const SpellCreatedObstacle*>(oi.get());
@ -83,7 +84,6 @@ void BattleObstacleController::obstaclePlaced(const std::vector<std::shared_ptr<
if (!spellObstacle)
{
logGlobal->error("I don't know how to animate appearing obstacle of type %d", (int)oi->obstacleType);
obstaclesBeingPlaced.erase(obstaclesBeingPlaced.begin());
continue;
}
@ -92,29 +92,22 @@ void BattleObstacleController::obstaclePlaced(const std::vector<std::shared_ptr<
auto first = animation->getImage(0, 0);
if(!first)
{
obstaclesBeingPlaced.erase(obstaclesBeingPlaced.begin());
continue;
}
//TODO: sound
//soundBase::QUIKSAND
//soundBase::LANDMINE
//we assume here that effect graphics have the same size as the usual obstacle image
// -> if we know how to blit obstacle, let's blit the effect in the same place
Point whereTo = getObstaclePosition(first, *oi);
owner.stacksController->addNewAnim(new CPointEffectAnimation(owner, soundBase::invalid, spellObstacle->appearAnimation, whereTo, oi->pos, CPointEffectAnimation::WAIT_FOR_SOUND));
CCS->soundh->playSound( spellObstacle->appearSound );
owner.stacksController->addNewAnim(new EffectAnimation(owner, spellObstacle->appearAnimation, whereTo, oi->pos));
//so when multiple obstacles are added, they show up one after another
owner.waitForAnims();
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
obstaclesBeingPlaced.erase(obstaclesBeingPlaced.begin());
loadObstacleImage(*spellObstacle);
}
}
void BattleObstacleController::showAbsoluteObstacles(Canvas & canvas, const Point & offset)
void BattleObstacleController::showAbsoluteObstacles(Canvas & canvas)
{
//Blit absolute obstacles
for(auto & oi : owner.curInt->cb->battleGetAllObstacles())
@ -123,7 +116,7 @@ void BattleObstacleController::showAbsoluteObstacles(Canvas & canvas, const Poin
{
auto img = getObstacleImage(*oi);
if(img)
canvas.draw(img, Point(offset.x + oi->getInfo().width, offset.y + oi->getInfo().height));
canvas.draw(img, Point(oi->getInfo().width, oi->getInfo().height));
}
}
}
@ -149,31 +142,26 @@ void BattleObstacleController::collectRenderableObjects(BattleRenderer & rendere
}
}
void BattleObstacleController::update()
{
timePassed += GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
}
std::shared_ptr<IImage> BattleObstacleController::getObstacleImage(const CObstacleInstance & oi)
{
int frameIndex = (owner.animCount+1) *25 / owner.getAnimSpeed();
int framesCount = timePassed * AnimationControls::getObstaclesSpeed();
std::shared_ptr<CAnimation> animation;
// obstacle is not loaded yet, don't show anything
if (obstacleAnimations.count(oi.uniqueID) == 0)
{
if (boost::range::find(obstaclesBeingPlaced, oi.uniqueID) != obstaclesBeingPlaced.end())
{
// obstacle is not loaded yet, don't show anything
return nullptr;
}
else
{
assert(0); // how?
loadObstacleImage(oi);
}
}
return nullptr;
animation = obstacleAnimations[oi.uniqueID];
assert(animation);
if(animation)
{
frameIndex %= animation->size(0);
int frameIndex = framesCount % animation->size(0);
return animation->getImage(frameIndex, 0);
}
return nullptr;
@ -183,7 +171,7 @@ Point BattleObstacleController::getObstaclePosition(std::shared_ptr<IImage> imag
{
int offset = obstacle.getAnimationYOffset(image->height());
Rect r = owner.fieldController->hexPositionAbsolute(obstacle.pos);
Rect r = owner.fieldController->hexPositionLocal(obstacle.pos);
r.y += 42 - image->height() + offset;
return r.topLeft();

View File

@ -29,16 +29,15 @@ class BattleObstacleController
{
BattleInterface & owner;
/// total time, in seconds, since start of battle. Used for animating obstacles
float timePassed;
/// cached animations of all obstacles in current battle
std::map<std::string, std::shared_ptr<CAnimation>> animationsCache;
/// list of all obstacles that are currently being rendered
std::map<si32, std::shared_ptr<CAnimation>> obstacleAnimations;
/// semi-debug member, contains obstacles that should not yet be visible due to ongoing placement animation
/// used only for sanity checks to ensure that there are no invisible obstacles
std::vector<si32> obstaclesBeingPlaced;
void loadObstacleImage(const CObstacleInstance & oi);
std::shared_ptr<IImage> getObstacleImage(const CObstacleInstance & oi);
@ -47,11 +46,14 @@ class BattleObstacleController
public:
BattleObstacleController(BattleInterface & owner);
/// called every frame
void update();
/// call-in from network pack, add newly placed obstacles with any required animations
void obstaclePlaced(const std::vector<std::shared_ptr<const CObstacleInstance>> & oi);
/// renders all "absolute" obstacles
void showAbsoluteObstacles(Canvas & canvas, const Point & offset);
void showAbsoluteObstacles(Canvas & canvas);
/// adds all non-"absolute" visible obstacles for rendering
void collectRenderableObjects(BattleRenderer & renderer);

View File

@ -48,22 +48,21 @@ static double calculateCatapultParabolaY(const Point & from, const Point & dest,
void ProjectileMissile::show(Canvas & canvas)
{
logAnim->info("Projectile rendering, %d / %d", step, steps);
size_t group = reverse ? 1 : 0;
auto image = animation->getImage(frameNum, group, true);
if(image)
{
float progress = float(step) / steps;
Point pos {
CSDL_Ext::lerp(from.x, dest.x, progress) - image->width() / 2,
CSDL_Ext::lerp(from.y, dest.y, progress) - image->height() / 2,
vstd::lerp(from.x, dest.x, progress) - image->width() / 2,
vstd::lerp(from.y, dest.y, progress) - image->height() / 2,
};
canvas.draw(image, pos);
}
++step;
float timePassed = GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
progress += timePassed * speed;
}
void ProjectileAnimatedMissile::show(Canvas & canvas)
@ -83,9 +82,7 @@ void ProjectileCatapult::show(Canvas & canvas)
if(image)
{
float progress = float(step) / steps;
int posX = CSDL_Ext::lerp(from.x, dest.x, progress);
int posX = vstd::lerp(from.x, dest.x, progress);
int posY = calculateCatapultParabolaY(from, dest, posX);
Point pos(posX, posY);
@ -93,16 +90,16 @@ void ProjectileCatapult::show(Canvas & canvas)
frameNum = (frameNum + 1) % animation->size(0);
}
++step;
float timePassed = GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
progress += timePassed * speed;
}
void ProjectileRay::show(Canvas & canvas)
{
float progress = float(step) / steps;
Point curr {
CSDL_Ext::lerp(from.x, dest.x, progress),
CSDL_Ext::lerp(from.y, dest.y, progress),
vstd::lerp(from.x, dest.x, progress),
vstd::lerp(from.y, dest.y, progress),
};
Point length = curr - from;
@ -143,7 +140,9 @@ void ProjectileRay::show(Canvas & canvas)
canvas.drawLine(Point(x1 + i, y1), Point(x2 + i, y2), beginColor, endColor);
}
}
++step;
float timePassed = GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
progress += timePassed * speed;
}
BattleProjectileController::BattleProjectileController(BattleInterface & owner):
@ -232,17 +231,17 @@ void BattleProjectileController::showProjectiles(Canvas & canvas)
}
vstd::erase_if(projectiles, [&](const std::shared_ptr<ProjectileBase> & projectile){
return projectile->step > projectile->steps;
return projectile->progress > 1.0f;
});
}
bool BattleProjectileController::hasActiveProjectile(const CStack * stack) const
bool BattleProjectileController::hasActiveProjectile(const CStack * stack, bool emittedOnly) const
{
int stackID = stack ? stack->ID : -1;
for(auto const & instance : projectiles)
{
if(instance->shooterID == stackID)
if(instance->shooterID == stackID && (instance->playing || !emittedOnly))
{
return true;
}
@ -250,15 +249,14 @@ bool BattleProjectileController::hasActiveProjectile(const CStack * stack) const
return false;
}
int BattleProjectileController::computeProjectileFlightTime( Point from, Point dest, double animSpeed)
float BattleProjectileController::computeProjectileFlightTime( Point from, Point dest, double animSpeed)
{
double distanceSquared = (dest.x - from.x) * (dest.x - from.x) + (dest.y - from.y) * (dest.y - from.y);
double distance = sqrt(distanceSquared);
int steps = std::round(distance / animSpeed);
float distanceSquared = (dest.x - from.x) * (dest.x - from.x) + (dest.y - from.y) * (dest.y - from.y);
float distance = sqrt(distanceSquared);
if (steps > 0)
return steps;
return 1;
assert(distance > 1.f);
return animSpeed / std::max( 1.f, distance);
}
int BattleProjectileController::computeProjectileFrameID( Point from, Point dest, const CStack * stack)
@ -298,12 +296,11 @@ void BattleProjectileController::createCatapultProjectile(const CStack * shooter
catapultProjectile->animation = getProjectileImage(shooter);
catapultProjectile->frameNum = 0;
catapultProjectile->step = 0;
catapultProjectile->steps = computeProjectileFlightTime(from, dest, AnimationControls::getCatapultSpeed());
catapultProjectile->progress = 0;
catapultProjectile->speed = computeProjectileFlightTime(from, dest, AnimationControls::getCatapultSpeed());
catapultProjectile->from = from;
catapultProjectile->dest = dest;
catapultProjectile->shooterID = shooter->ID;
catapultProjectile->step = 0;
catapultProjectile->playing = false;
projectiles.push_back(std::shared_ptr<ProjectileBase>(catapultProjectile));
@ -336,11 +333,11 @@ void BattleProjectileController::createProjectile(const CStack * shooter, Point
missileProjectile->frameNum = computeProjectileFrameID(from, dest, shooter);
}
projectile->steps = computeProjectileFlightTime(from, dest, AnimationControls::getProjectileSpeed());
projectile->speed = computeProjectileFlightTime(from, dest, AnimationControls::getProjectileSpeed());
projectile->from = from;
projectile->dest = dest;
projectile->shooterID = shooter->ID;
projectile->step = 0;
projectile->progress = 0;
projectile->playing = false;
projectiles.push_back(projectile);
@ -364,8 +361,8 @@ void BattleProjectileController::createSpellProjectile(const CStack * shooter, P
projectile->from = from;
projectile->dest = dest;
projectile->shooterID = shooter ? shooter->ID : -1;
projectile->step = 0;
projectile->steps = computeProjectileFlightTime(from, dest, AnimationControls::getSpellEffectSpeed());
projectile->progress = 0;
projectile->speed = computeProjectileFlightTime(from, dest, AnimationControls::getProjectileSpeed());
projectile->playing = false;
projectiles.push_back(std::shared_ptr<ProjectileBase>(projectile));

View File

@ -33,10 +33,10 @@ struct ProjectileBase
Point from; // initial position on the screen
Point dest; // target position on the screen
int step; // current step counter
int steps; // total number of steps/frames to show
int shooterID; // ID of shooter stack
bool playing; // if set to true, projectile animation is playing, e.g. flying to target
float progress; // current position of projectile on from->dest line
float speed; // how much progress is gained per second
int shooterID; // ID of shooter stack
bool playing; // if set to true, projectile animation is playing, e.g. flying to target
};
/// Projectile for most shooters - render pre-selected frame moving in straight line from origin to destination
@ -97,7 +97,7 @@ class BattleProjectileController
const CCreature & getShooter(const CStack * stack) const;
int computeProjectileFrameID( Point from, Point dest, const CStack * stack);
int computeProjectileFlightTime( Point from, Point dest, double speed);
float computeProjectileFlightTime( Point from, Point dest, double speed);
public:
BattleProjectileController(BattleInterface & owner);
@ -106,7 +106,7 @@ public:
void showProjectiles(Canvas & canvas);
/// returns true if stack has projectile that is yet to hit target
bool hasActiveProjectile(const CStack * stack) const;
bool hasActiveProjectile(const CStack * stack, bool emittedOnly) const;
/// starts rendering previously created projectile
void emitStackProjectile(const CStack * stack);

View File

@ -11,19 +11,24 @@
#include "BattleRenderer.h"
#include "BattleInterface.h"
#include "BattleInterfaceClasses.h"
#include "BattleEffectsController.h"
#include "BattleWindow.h"
#include "BattleSiegeController.h"
#include "BattleStacksController.h"
#include "BattleObstacleController.h"
void BattleRenderer::collectObjects()
{
owner.collectRenderableObjects(*this);
owner.effectsController->collectRenderableObjects(*this);
owner.obstacleController->collectRenderableObjects(*this);
owner.stacksController->collectRenderableObjects(*this);
if (owner.siegeController)
owner.siegeController->collectRenderableObjects(*this);
if (owner.defendingHero)
owner.defendingHero->collectRenderableObjects(*this);
if (owner.attackingHero)
owner.attackingHero->collectRenderableObjects(*this);
}
void BattleRenderer::sortObjects()

View File

@ -19,11 +19,11 @@ enum class EBattleFieldLayer {
OBSTACLES = 0,
CORPSES = 0,
WALLS = 1,
HEROES = 1,
STACKS = 1, // after corpses, obstacles
BATTLEMENTS = 2, // after stacks
STACK_AMOUNTS = 2, // after stacks, obstacles, corpses
EFFECTS = 3, // after obstacles, battlements
HEROES = 2,
STACKS = 2, // after corpses, obstacles, walls
BATTLEMENTS = 3, // after stacks
STACK_AMOUNTS = 3, // after stacks, obstacles, corpses
EFFECTS = 4, // after obstacles, battlements
};
class BattleRenderer

View File

@ -106,13 +106,13 @@ std::string BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisua
}
}
void BattleSiegeController::showWallPiece(Canvas & canvas, EWallVisual::EWallVisual what, const Point & offset)
void BattleSiegeController::showWallPiece(Canvas & canvas, EWallVisual::EWallVisual what)
{
auto & ci = town->town->clientInfo;
auto const & pos = ci.siegePositions[what];
if ( wallPieceImages[what])
canvas.draw(wallPieceImages[what], offset + Point(pos.x, pos.y));
canvas.draw(wallPieceImages[what], Point(pos.x, pos.y));
}
std::string BattleSiegeController::getBattleBackgroundName() const
@ -146,7 +146,7 @@ BattleHex BattleSiegeController::getWallPiecePosition(EWallVisual::EWallVisual w
BattleHex::HEX_AFTER_ALL, // BOTTOM_TOWER,
182, // BOTTOM_WALL,
130, // WALL_BELLOW_GATE,
78, // WALL_OVER_GATE,
62, // WALL_OVER_GATE,
12, // UPPER_WALL,
BattleHex::HEX_BEFORE_ALL, // UPPER_TOWER,
BattleHex::HEX_BEFORE_ALL, // GATE, // 94
@ -205,10 +205,10 @@ Point BattleSiegeController::getTurretCreaturePosition( BattleHex position ) con
if (posID != 0)
{
Point result = owner.pos.topLeft();
result.x += town->town->clientInfo.siegePositions[posID].x;
result.y += town->town->clientInfo.siegePositions[posID].y;
return result;
return {
town->town->clientInfo.siegePositions[posID].x,
town->town->clientInfo.siegePositions[posID].y
};
}
assert(0);
@ -249,13 +249,13 @@ void BattleSiegeController::gateStateChanged(const EGateState state)
CCS->soundh->playSound(soundBase::DRAWBRG);
}
void BattleSiegeController::showAbsoluteObstacles(Canvas & canvas, const Point & offset)
void BattleSiegeController::showAbsoluteObstacles(Canvas & canvas)
{
if (getWallPieceExistance(EWallVisual::MOAT))
showWallPiece(canvas, EWallVisual::MOAT, offset);
showWallPiece(canvas, EWallVisual::MOAT);
if (getWallPieceExistance(EWallVisual::MOAT_BANK))
showWallPiece(canvas, EWallVisual::MOAT_BANK, offset);
showWallPiece(canvas, EWallVisual::MOAT_BANK);
}
BattleHex BattleSiegeController::getTurretBattleHex(EWallVisual::EWallVisual wallPiece) const
@ -301,11 +301,11 @@ void BattleSiegeController::collectRenderableObjects(BattleRenderer & renderer)
owner.stacksController->showStack(canvas, getTurretStack(wallPiece));
});
renderer.insert( EBattleFieldLayer::BATTLEMENTS, getWallPiecePosition(wallPiece), [this, wallPiece](BattleRenderer::RendererRef canvas){
showWallPiece(canvas, wallPiece, owner.pos.topLeft());
showWallPiece(canvas, wallPiece);
});
}
renderer.insert( EBattleFieldLayer::WALLS, getWallPiecePosition(wallPiece), [this, wallPiece](BattleRenderer::RendererRef canvas){
showWallPiece(canvas, wallPiece, owner.pos.topLeft());
showWallPiece(canvas, wallPiece);
});
@ -327,12 +327,14 @@ bool BattleSiegeController::isAttackableByCatapult(BattleHex hex) const
void BattleSiegeController::stackIsCatapulting(const CatapultAttack & ca)
{
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);
if (ca.attacker != -1)
{
const CStack *stack = owner.curInt->cb->battleGetStackByID(ca.attacker);
for (auto attackInfo : ca.attackedParts)
{
owner.stacksController->addNewAnim(new CCatapultAnimation(owner, stack, attackInfo.destinationTile, nullptr, attackInfo.damageDealt));
owner.stacksController->addNewAnim(new CatapultAnimation(owner, stack, attackInfo.destinationTile, nullptr, attackInfo.damageDealt));
}
}
else
@ -343,11 +345,12 @@ void BattleSiegeController::stackIsCatapulting(const CatapultAttack & ca)
for (auto attackInfo : ca.attackedParts)
positions.push_back(owner.stacksController->getStackPositionAtHex(attackInfo.destinationTile, nullptr) + Point(99, 120));
owner.stacksController->addNewAnim(new CPointEffectAnimation(owner, soundBase::WALLHIT, "SGEXPL.DEF", positions));
CCS->soundh->playSound( "WALLHIT" );
owner.stacksController->addNewAnim(new EffectAnimation(owner, "SGEXPL.DEF", positions));
}
owner.waitForAnims();
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
owner.setAnimationCondition(EAnimationEvents::HIT, false);
for (auto attackInfo : ca.attackedParts)
{

View File

@ -84,7 +84,7 @@ class BattleSiegeController
/// returns true if chosen wall piece should be present in current battle
bool getWallPieceExistance(EWallVisual::EWallVisual what) const;
void showWallPiece(Canvas & canvas, EWallVisual::EWallVisual what, const Point & offset);
void showWallPiece(Canvas & canvas, EWallVisual::EWallVisual what);
BattleHex getTurretBattleHex(EWallVisual::EWallVisual wallPiece) const;
const CStack * getTurretStack(EWallVisual::EWallVisual wallPiece) const;
@ -97,7 +97,7 @@ public:
void stackIsCatapulting(const CatapultAttack & ca);
/// call-ins from other battle controllers
void showAbsoluteObstacles(Canvas & canvas, const Point & offset);
void showAbsoluteObstacles(Canvas & canvas);
void collectRenderableObjects(BattleRenderer & renderer);
/// queries from other battle controllers

View File

@ -13,11 +13,12 @@
#include "BattleSiegeController.h"
#include "BattleInterfaceClasses.h"
#include "BattleInterface.h"
#include "BattleActionsController.h"
#include "BattleAnimationClasses.h"
#include "BattleFieldController.h"
#include "BattleEffectsController.h"
#include "BattleProjectileController.h"
#include "BattleControlPanel.h"
#include "BattleWindow.h"
#include "BattleRenderer.h"
#include "CreatureAnimation.h"
@ -27,6 +28,7 @@
#include "../gui/CAnimation.h"
#include "../gui/CGuiHandler.h"
#include "../gui/Canvas.h"
#include "../../lib/spells/ISpellMechanics.h"
#include "../../CCallback.h"
#include "../../lib/battle/BattleHex.h"
@ -40,20 +42,26 @@ static void onAnimationFinished(const CStack *stack, std::weak_ptr<CreatureAnima
if(!animation)
return;
if (!stack->isFrozen() && animation->getType() == ECreatureAnimType::FROZEN)
animation->setType(ECreatureAnimType::HOLDING);
if (animation->isIdle())
{
const CCreature *creature = stack->getCreature();
if (animation->framesInGroup(CCreatureAnim::MOUSEON) > 0)
if (stack->isFrozen())
animation->setType(ECreatureAnimType::FROZEN);
else
if (animation->framesInGroup(ECreatureAnimType::MOUSEON) > 0)
{
if (CRandomGenerator::getDefault().nextDouble(99.0) < creature->animation.timeBetweenFidgets *10)
animation->playOnce(CCreatureAnim::MOUSEON);
animation->playOnce(ECreatureAnimType::MOUSEON);
else
animation->setType(CCreatureAnim::HOLDING);
animation->setType(ECreatureAnimType::HOLDING);
}
else
{
animation->setType(CCreatureAnim::HOLDING);
animation->setType(ECreatureAnimType::HOLDING);
}
}
// always reset callback
@ -63,7 +71,6 @@ static void onAnimationFinished(const CStack *stack, std::weak_ptr<CreatureAnima
BattleStacksController::BattleStacksController(BattleInterface & owner):
owner(owner),
activeStack(nullptr),
mouseHoveredStack(nullptr),
stackToActivate(nullptr),
selectedStack(nullptr),
stackCanCastSpell(false),
@ -76,20 +83,26 @@ BattleStacksController::BattleStacksController(BattleInterface & owner):
amountNegative = IImage::createFromFile("CMNUMWIN.BMP");
amountEffNeutral = IImage::createFromFile("CMNUMWIN.BMP");
static const ColorShifterMultiplyAndAddExcept shifterNormal ({150, 50, 255, 255}, {0,0,0,0}, {255, 231, 132, 255});
static const ColorShifterMultiplyAndAddExcept shifterPositive({ 50, 255, 50, 255}, {0,0,0,0}, {255, 231, 132, 255});
static const ColorShifterMultiplyAndAddExcept shifterNegative({255, 50, 50, 255}, {0,0,0,0}, {255, 231, 132, 255});
static const ColorShifterMultiplyAndAddExcept shifterNeutral ({255, 255, 50, 255}, {0,0,0,0}, {255, 231, 132, 255});
static const auto shifterNormal = ColorFilter::genRangeShifter( 0,0,0, 0.6, 0.2, 1.0 );
static const auto shifterPositive = ColorFilter::genRangeShifter( 0,0,0, 0.2, 1.0, 0.2 );
static const auto shifterNegative = ColorFilter::genRangeShifter( 0,0,0, 1.0, 0.2, 0.2 );
static const auto shifterNeutral = ColorFilter::genRangeShifter( 0,0,0, 1.0, 1.0, 0.2 );
amountNormal->adjustPalette(&shifterNormal);
amountPositive->adjustPalette(&shifterPositive);
amountNegative->adjustPalette(&shifterNegative);
amountEffNeutral->adjustPalette(&shifterNeutral);
amountNormal->adjustPalette(shifterNormal);
amountPositive->adjustPalette(shifterPositive);
amountNegative->adjustPalette(shifterNegative);
amountEffNeutral->adjustPalette(shifterNeutral);
//Restore border color {255, 231, 132, 255} to its original state
amountNormal->resetPalette(26);
amountPositive->resetPalette(26);
amountNegative->resetPalette(26);
amountEffNeutral->resetPalette(26);
std::vector<const CStack*> stacks = owner.curInt->cb->battleGetAllStacks(true);
for(const CStack * s : stacks)
{
stackAdded(s);
stackAdded(s, true);
}
}
@ -98,7 +111,7 @@ BattleHex BattleStacksController::getStackCurrentPosition(const CStack * stack)
if ( !stackAnimation.at(stack->ID)->isMoving())
return stack->getPosition();
if (stack->hasBonusOfType(Bonus::FLYING))
if (stack->hasBonusOfType(Bonus::FLYING) && stackAnimation.at(stack->ID)->getType() == ECreatureAnimType::MOVING )
return BattleHex::HEX_AFTER_ALL;
for (auto & anim : currentAnimations)
@ -107,10 +120,10 @@ BattleHex BattleStacksController::getStackCurrentPosition(const CStack * stack)
// stack position will be updated only *after* movement is finished
// before this - stack is always at its initial position. Thus we need to find
// its current position. Which can be found only in this class
if (CStackMoveAnimation *move = dynamic_cast<CStackMoveAnimation*>(anim))
if (StackMoveAnimation *move = dynamic_cast<StackMoveAnimation*>(anim))
{
if (move->stack == stack)
return move->currentHex;
return std::max(move->prevHex, move->nextHex);
}
}
return stack->getPosition();
@ -126,7 +139,7 @@ void BattleStacksController::collectRenderableObjects(BattleRenderer & renderer)
continue;
//FIXME: hack to ignore ghost stacks
if ((stackAnimation[stack->ID]->getType() == CCreatureAnim::DEAD || stackAnimation[stack->ID]->getType() == CCreatureAnim::HOLDING) && stack->isGhost())
if ((stackAnimation[stack->ID]->getType() == ECreatureAnimType::DEAD || stackAnimation[stack->ID]->getType() == ECreatureAnimType::HOLDING) && stack->isGhost())
continue;
auto layer = stackAnimation[stack->ID]->isDead() ? EBattleFieldLayer::CORPSES : EBattleFieldLayer::STACKS;
@ -147,6 +160,11 @@ void BattleStacksController::collectRenderableObjects(BattleRenderer & renderer)
void BattleStacksController::stackReset(const CStack * stack)
{
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);
//reset orientation?
//stackFacingRight[stack->ID] = stack->side == BattleSide::ATTACKER;
auto iter = stackAnimation.find(stack->ID);
if(iter == stackAnimation.end())
@ -158,22 +176,18 @@ void BattleStacksController::stackReset(const CStack * stack)
auto animation = iter->second;
if(stack->alive() && animation->isDeadOrDying())
animation->setType(CCreatureAnim::HOLDING);
static const ColorShifterMultiplyAndAdd shifterClone ({255, 255, 0, 255}, {0, 0, 255, 0});
if (stack->isClone())
{
animation->shiftColor(&shifterClone);
owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=]()
{
addNewAnim(new ResurrectionAnimation(owner, stack));
});
}
//TODO: handle more cases
}
void BattleStacksController::stackAdded(const CStack * stack)
void BattleStacksController::stackAdded(const CStack * stack, bool instant)
{
// Tower shooters have only their upper half visible
static const int turretCreatureAnimationHeight = 235;
static const int turretCreatureAnimationHeight = 225;
stackFacingRight[stack->ID] = stack->side == BattleSide::ATTACKER; // must be set before getting stack position
@ -199,7 +213,21 @@ void BattleStacksController::stackAdded(const CStack * stack)
stackAnimation[stack->ID]->pos.x = coords.x;
stackAnimation[stack->ID]->pos.y = coords.y;
stackAnimation[stack->ID]->pos.w = stackAnimation[stack->ID]->getWidth();
stackAnimation[stack->ID]->setType(CCreatureAnim::HOLDING);
stackAnimation[stack->ID]->setType(ECreatureAnimType::HOLDING);
if (!instant)
{
// immediately make stack transparent, giving correct shifter time to start
auto shifterFade = ColorFilter::genAlphaShifter(0);
setStackColorFilter(shifterFade, stack, nullptr, true);
owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=]()
{
addNewAnim(new ColorTransformAnimation(owner, stack, "summonFadeIn", nullptr));
if (stack->isClone())
addNewAnim(new ColorTransformAnimation(owner, stack, "cloning", SpellID(SpellID::CLONE).toSpell() ));
});
}
}
void BattleStacksController::setActiveStack(const CStack *stack)
@ -212,31 +240,7 @@ void BattleStacksController::setActiveStack(const CStack *stack)
if (activeStack) // update UI
stackAnimation[activeStack->ID]->setBorderColor(AnimationControls::getGoldBorder());
owner.controlPanel->blockUI(activeStack == nullptr);
}
void BattleStacksController::setHoveredStack(const CStack *stack)
{
if ( stack == mouseHoveredStack )
return;
if (mouseHoveredStack)
stackAnimation[mouseHoveredStack->ID]->setBorderColor(AnimationControls::getNoBorder());
// stack must be alive and not active (which uses gold border instead)
if (stack && stack->alive() && stack != activeStack)
{
mouseHoveredStack = stack;
if (mouseHoveredStack)
{
stackAnimation[mouseHoveredStack->ID]->setBorderColor(AnimationControls::getBlueBorder());
if (stackAnimation[mouseHoveredStack->ID]->framesInGroup(CCreatureAnim::MOUSEON) > 0)
stackAnimation[mouseHoveredStack->ID]->playOnce(CCreatureAnim::MOUSEON);
}
}
else
mouseHoveredStack = nullptr;
owner.windowObject->blockUI(activeStack == nullptr);
}
bool BattleStacksController::stackNeedsAmountBox(const CStack * stack) const
@ -249,39 +253,25 @@ bool BattleStacksController::stackNeedsAmountBox(const CStack * stack) const
currentActionTarget = target.at(0).hexValue;
}
if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON) && stack->getCount() == 1) //do not show box for singular war machines, stacked war machines with box shown are supported as extension feature
//do not show box for singular war machines, stacked war machines with box shown are supported as extension feature
if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON) && stack->getCount() == 1)
return false;
if (!owner.battleActionsStarted) // do not perform any further checks since they are related to actions that will only occur after intro music
return true;
if(!stack->alive())
return false;
if(stack->getCount() == 0) //hide box when target is going to die anyway - do not display "0 creatures"
//hide box when target is going to die anyway - do not display "0 creatures"
if(stack->getCount() == 0)
return false;
for(auto anim : currentAnimations) //no matter what other conditions below are, hide box when creature is playing hit animation
// if stack has any ongoing animation - hide the box
for(auto anim : currentAnimations)
{
auto hitAnimation = dynamic_cast<CDefenceAnimation*>(anim);
if(hitAnimation && (hitAnimation->stack->ID == stack->ID))
auto stackAnimation = dynamic_cast<BattleStackAnimation*>(anim);
if(stackAnimation && (stackAnimation->stack->ID == stack->ID))
return false;
}
if(owner.curInt->curAction)
{
if(owner.curInt->curAction->stackNumber == stack->ID) //stack is currently taking action (is not a target of another creature's action etc)
{
if(owner.curInt->curAction->actionType == EActionType::WALK || owner.curInt->curAction->actionType == EActionType::SHOOT) //hide when stack walks or shoots
return false;
else if(owner.curInt->curAction->actionType == EActionType::WALK_AND_ATTACK && currentActionTarget != stack->getPosition()) //when attacking, hide until walk phase finished
return false;
}
if(owner.curInt->curAction->actionType == EActionType::SHOOT && currentActionTarget == stack->getPosition()) //hide if we are ranged attack target
return false;
}
return true;
}
@ -332,46 +322,65 @@ void BattleStacksController::showStackAmountBox(Canvas & canvas, const CStack *
void BattleStacksController::showStack(Canvas & canvas, const CStack * stack)
{
stackAnimation[stack->ID]->nextFrame(canvas, facingRight(stack)); // do actual blit
ColorFilter fullFilter = ColorFilter::genEmptyShifter();
for (auto const & filter : stackFilterEffects)
{
if (filter.target == stack)
fullFilter = ColorFilter::genCombined(fullFilter, filter.effect);
}
bool stackHasProjectile = owner.projectilesController->hasActiveProjectile(stack, true);
if (stackHasProjectile)
stackAnimation[stack->ID]->pause();
else
stackAnimation[stack->ID]->play();
stackAnimation[stack->ID]->nextFrame(canvas, fullFilter, facingRight(stack)); // do actual blit
stackAnimation[stack->ID]->incrementFrame(float(GH.mainFPSmng->getElapsedMilliseconds()) / 1000);
}
void BattleStacksController::update()
{
updateHoveredStacks();
updateBattleAnimations();
}
void BattleStacksController::initializeBattleAnimations()
{
auto copiedVector = currentAnimations;
for (auto & elem : copiedVector)
if (elem && !elem->isInitialized())
elem->tryInitialize();
}
void BattleStacksController::stepFrameBattleAnimations()
{
// operate on copy - to prevent potential iterator invalidation due to push_back's
// FIXME? : remove remaining calls to addNewAnim from BattleAnimation::nextFrame (only Catapult explosion at the time of writing)
auto copiedVector = currentAnimations;
for (auto & elem : copiedVector)
if (elem && elem->isInitialized())
elem->nextFrame();
}
void BattleStacksController::updateBattleAnimations()
{
for (auto & elem : currentAnimations)
{
if (!elem)
continue;
if (elem->isInitialized())
elem->nextFrame();
else
elem->tryInitialize();
}
bool hadAnimations = !currentAnimations.empty();
initializeBattleAnimations();
stepFrameBattleAnimations();
vstd::erase(currentAnimations, nullptr);
if (hadAnimations && currentAnimations.empty())
{
//anims ended
owner.controlPanel->blockUI(activeStack == nullptr);
owner.animsAreDisplayed.setn(false);
}
owner.setAnimationCondition(EAnimationEvents::ACTION, false);
initializeBattleAnimations();
}
void BattleStacksController::addNewAnim(CBattleAnimation *anim)
void BattleStacksController::addNewAnim(BattleAnimation *anim)
{
currentAnimations.push_back(anim);
owner.animsAreDisplayed.setn(true);
}
void BattleStacksController::stackActivated(const CStack *stack) //TODO: check it all before game state is changed due to abilities
{
stackToActivate = stack;
owner.waitForAnims();
if (stackToActivate) //during waiting stack may have gotten activated through show
owner.activateStack();
owner.setAnimationCondition(EAnimationEvents::ACTION, true);
}
void BattleStacksController::stackRemoved(uint32_t stackID)
@ -389,44 +398,275 @@ void BattleStacksController::stackRemoved(uint32_t stackID)
void BattleStacksController::stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos)
{
owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){
// remove any potentially erased petrification effect
removeExpiredColorFilters();
});
for(auto & attackedInfo : attackedInfos)
{
//if (!attackedInfo.cloneKilled) //FIXME: play dead animation for cloned creature before it vanishes
addNewAnim(new CDefenceAnimation(attackedInfo, owner));
if (!attackedInfo.attacker)
continue;
if(attackedInfo.rebirth)
// In H3, attacked stack will not reverse on ranged attack
if (attackedInfo.indirectAttack)
continue;
// Another type of indirect attack - dragon breath
if (!CStack::isMeleeAttackPossible(attackedInfo.attacker, attackedInfo.defender))
continue;
// defender need to face in direction opposited to out attacker
bool needsReverse = shouldAttackFacingRight(attackedInfo.attacker, attackedInfo.defender) == facingRight(attackedInfo.defender);
// FIXME: this check is better, however not usable since stacksAreAttacked is called after net pack is applyed - petrification is already removed
// if (needsReverse && !attackedInfo.defender->isFrozen())
if (needsReverse && stackAnimation[attackedInfo.defender->ID]->getType() != ECreatureAnimType::FROZEN)
{
owner.effectsController->displayEffect(EBattleEffect::RESURRECT, soundBase::RESURECT, attackedInfo.defender->getPosition()); //TODO: play reverse death animation
owner.executeOnAnimationCondition(EAnimationEvents::MOVEMENT, true, [=]()
{
addNewAnim(new ReverseAnimation(owner, attackedInfo.defender, attackedInfo.defender->getPosition()));
});
}
}
owner.waitForAnims();
for(auto & attackedInfo : attackedInfos)
{
bool useDeathAnim = attackedInfo.killed;
bool useDefenceAnim = attackedInfo.defender->defendingAnim && !attackedInfo.indirectAttack && !attackedInfo.killed;
EAnimationEvents usedEvent = useDefenceAnim ? EAnimationEvents::ATTACK : EAnimationEvents::HIT;
owner.executeOnAnimationCondition(usedEvent, true, [=]()
{
if (useDeathAnim)
addNewAnim(new DeathAnimation(owner, attackedInfo.defender, attackedInfo.indirectAttack));
else if(useDefenceAnim)
addNewAnim(new DefenceAnimation(owner, attackedInfo.defender));
else
addNewAnim(new HittedAnimation(owner, attackedInfo.defender));
if (attackedInfo.fireShield)
owner.effectsController->displayEffect(EBattleEffect::FIRE_SHIELD, "FIRESHIE", attackedInfo.attacker->getPosition());
if (attackedInfo.spellEffect != SpellID::NONE)
{
auto spell = attackedInfo.spellEffect.toSpell();
if (!spell->getCastSound().empty())
CCS->soundh->playSound(spell->getCastSound());
owner.displaySpellEffect(spell, attackedInfo.defender->getPosition());
}
});
}
for (auto & attackedInfo : attackedInfos)
{
if (attackedInfo.rebirth)
stackAnimation[attackedInfo.defender->ID]->setType(CCreatureAnim::HOLDING);
if (attackedInfo.cloneKilled)
stackRemoved(attackedInfo.defender->ID);
{
owner.executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=](){
owner.effectsController->displayEffect(EBattleEffect::RESURRECT, "RESURECT", attackedInfo.defender->getPosition());
addNewAnim(new ResurrectionAnimation(owner, attackedInfo.defender));
});
}
if (attackedInfo.killed && attackedInfo.defender->summoned)
{
owner.executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=](){
addNewAnim(new ColorTransformAnimation(owner, attackedInfo.defender, "summonFadeOut", nullptr));
stackRemoved(attackedInfo.defender->ID);
});
}
}
executeAttackAnimations();
}
void BattleStacksController::stackTeleported(const CStack *stack, std::vector<BattleHex> destHex, int distance)
{
assert(destHex.size() > 0);
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);
owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=](){
addNewAnim( new ColorTransformAnimation(owner, stack, "teleportFadeOut", nullptr) );
});
owner.executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=](){
stackAnimation[stack->ID]->pos.moveTo(getStackPositionAtHex(destHex.back(), stack));
addNewAnim( new ColorTransformAnimation(owner, stack, "teleportFadeIn", nullptr) );
});
// animations will be executed by spell
}
void BattleStacksController::stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance)
{
addNewAnim(new CMovementAnimation(owner, stack, destHex, distance));
owner.waitForAnims();
assert(destHex.size() > 0);
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);
bool stackTeleports = stack->hasBonus(Selector::typeSubtype(Bonus::FLYING, 1));
owner.setAnimationCondition(EAnimationEvents::MOVEMENT, true);
auto enqueMoveEnd = [&](){
addNewAnim(new MovementEndAnimation(owner, stack, destHex.back()));
owner.executeOnAnimationCondition(EAnimationEvents::ACTION, false, [&](){
owner.setAnimationCondition(EAnimationEvents::MOVEMENT, false);
});
};
auto enqueMove = [&](){
if (!stackTeleports)
{
addNewAnim(new MovementAnimation(owner, stack, destHex, distance));
owner.executeOnAnimationCondition(EAnimationEvents::ACTION, false, enqueMoveEnd);
}
else
enqueMoveEnd();
};
auto enqueMoveStart = [&](){
addNewAnim(new MovementStartAnimation(owner, stack));
owner.executeOnAnimationCondition(EAnimationEvents::ACTION, false, enqueMove);
};
if(shouldRotate(stack, stack->getPosition(), destHex[0]))
{
addNewAnim(new ReverseAnimation(owner, stack, stack->getPosition()));
owner.executeOnAnimationCondition(EAnimationEvents::ACTION, false, enqueMoveStart);
}
else
enqueMoveStart();
owner.waitForAnimationCondition(EAnimationEvents::MOVEMENT, false);
}
void BattleStacksController::stackAttacking( const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting )
bool BattleStacksController::shouldAttackFacingRight(const CStack * attacker, const CStack * defender)
{
if (shooting)
bool mustReverse = owner.curInt->cb->isToReverse(
attacker->getPosition(),
attacker,
defender);
if (attacker->side == BattleSide::ATTACKER)
return !mustReverse;
else
return mustReverse;
}
void BattleStacksController::stackAttacking( const StackAttackInfo & info )
{
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);
auto attacker = info.attacker;
auto defender = info.defender;
auto tile = info.tile;
auto spellEffect = info.spellEffect;
auto multiAttack = !info.secondaryDefender.empty();
bool needsReverse = false;
if (info.indirectAttack)
{
addNewAnim(new CShootingAnimation(owner, attacker, dest, attacked));
needsReverse = shouldRotate(attacker, attacker->position, info.tile);
}
else
{
addNewAnim(new CMeleeAttackAnimation(owner, attacker, dest, attacked));
needsReverse = shouldAttackFacingRight(attacker, defender) != facingRight(attacker);
}
//waitForAnims();
if (needsReverse)
{
owner.executeOnAnimationCondition(EAnimationEvents::MOVEMENT, true, [=]()
{
addNewAnim(new ReverseAnimation(owner, attacker, attacker->getPosition()));
});
}
if(info.lucky)
{
owner.executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() {
owner.appendBattleLog(info.attacker->formatGeneralMessage(-45));
owner.effectsController->displayEffect(EBattleEffect::GOOD_LUCK, "GOODLUCK", attacker->getPosition());
});
}
if(info.unlucky)
{
owner.executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() {
owner.appendBattleLog(info.attacker->formatGeneralMessage(-44));
owner.effectsController->displayEffect(EBattleEffect::BAD_LUCK, "BADLUCK", attacker->getPosition());
});
}
if(info.deathBlow)
{
owner.executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() {
owner.appendBattleLog(info.attacker->formatGeneralMessage(365));
owner.effectsController->displayEffect(EBattleEffect::DEATH_BLOW, "DEATHBLO", defender->getPosition());
});
for(auto elem : info.secondaryDefender)
{
owner.executeOnAnimationCondition(EAnimationEvents::BEFORE_HIT, true, [=]() {
owner.effectsController->displayEffect(EBattleEffect::DEATH_BLOW, elem->getPosition());
});
}
}
owner.executeOnAnimationCondition(EAnimationEvents::ATTACK, true, [=]()
{
if (info.indirectAttack)
{
addNewAnim(new ShootingAnimation(owner, attacker, tile, defender));
}
else
{
addNewAnim(new MeleeAttackAnimation(owner, attacker, tile, defender, multiAttack));
}
});
if (info.spellEffect != SpellID::NONE)
{
owner.executeOnAnimationCondition(EAnimationEvents::HIT, true, [=]()
{
owner.displaySpellHit(spellEffect.toSpell(), tile);
});
}
if (info.lifeDrain)
{
owner.executeOnAnimationCondition(EAnimationEvents::AFTER_HIT, true, [=]()
{
owner.effectsController->displayEffect(EBattleEffect::DRAIN_LIFE, "DRAINLIF", attacker->getPosition());
});
}
//return, animation playback will be handled by stacksAreAttacked
}
void BattleStacksController::executeAttackAnimations()
{
owner.setAnimationCondition(EAnimationEvents::MOVEMENT, true);
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
owner.setAnimationCondition(EAnimationEvents::MOVEMENT, false);
owner.setAnimationCondition(EAnimationEvents::BEFORE_HIT, true);
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
owner.setAnimationCondition(EAnimationEvents::BEFORE_HIT, false);
owner.setAnimationCondition(EAnimationEvents::ATTACK, true);
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
owner.setAnimationCondition(EAnimationEvents::ATTACK, false);
// Note that HIT event can also be emitted by attack animation
owner.setAnimationCondition(EAnimationEvents::HIT, true);
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
owner.setAnimationCondition(EAnimationEvents::HIT, false);
owner.setAnimationCondition(EAnimationEvents::AFTER_HIT, true);
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
owner.setAnimationCondition(EAnimationEvents::AFTER_HIT, false);
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
}
bool BattleStacksController::shouldRotate(const CStack * stack, const BattleHex & oldPos, const BattleHex & nextHex) const
@ -442,11 +682,11 @@ bool BattleStacksController::shouldRotate(const CStack * stack, const BattleHex
return false;
}
void BattleStacksController::endAction(const BattleAction* action)
{
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);
//check if we should reverse stacks
//for some strange reason, it's not enough
TStacks stacks = owner.curInt->cb->battleGetStacks(CBattleCallback::MINE_AND_ENEMY);
for (const CStack *s : stacks)
@ -455,29 +695,32 @@ void BattleStacksController::endAction(const BattleAction* action)
if (s && facingRight(s) != shouldFaceRight && s->alive() && stackAnimation[s->ID]->isIdle())
{
addNewAnim(new CReverseAnimation(owner, s, s->getPosition(), false));
addNewAnim(new ReverseAnimation(owner, s, s->getPosition()));
}
}
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
//Ensure that all animation flags were reset
assert(owner.getAnimationCondition(EAnimationEvents::OPENING) == false);
assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);
assert(owner.getAnimationCondition(EAnimationEvents::MOVEMENT) == false);
assert(owner.getAnimationCondition(EAnimationEvents::ATTACK) == false);
assert(owner.getAnimationCondition(EAnimationEvents::HIT) == false);
owner.windowObject->blockUI(activeStack == nullptr);
removeExpiredColorFilters();
}
void BattleStacksController::startAction(const BattleAction* action)
{
const CStack *stack = owner.curInt->cb->battleGetStackByID(action->stackNumber);
setHoveredStack(nullptr);
removeExpiredColorFilters();
}
auto actionTarget = action->getTarget(owner.curInt->cb.get());
if(action->actionType == EActionType::WALK
|| (action->actionType == EActionType::WALK_AND_ATTACK && actionTarget.at(0).hexValue != stack->getPosition()))
{
assert(stack);
owner.moveStarted = true;
if (stackAnimation[action->stackNumber]->framesInGroup(CCreatureAnim::MOVE_START))
addNewAnim(new CMovementStartAnimation(owner, stack));
//if(shouldRotate(stack, stack->getPosition(), actionTarget.at(0).hexValue))
// addNewAnim(new CReverseAnimation(owner, stack, stack->getPosition(), true));
}
void BattleStacksController::stackActivated(const CStack *stack)
{
stackToActivate = stack;
owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);
owner.activateStack();
}
void BattleStacksController::activateStack()
@ -584,6 +827,115 @@ Point BattleStacksController::getStackPositionAtHex(BattleHex hexNum, const CSta
}
}
//returning
return ret + owner.pos.topLeft();
return ret;
}
void BattleStacksController::setStackColorFilter(const ColorFilter & effect, const CStack * target, const CSpell * source, bool persistent)
{
for (auto & filter : stackFilterEffects)
{
if (filter.target == target && filter.source == source)
{
filter.effect = effect;
filter.persistent = persistent;
return;
}
}
stackFilterEffects.push_back({ effect, target, source, persistent });
}
void BattleStacksController::removeExpiredColorFilters()
{
vstd::erase_if(stackFilterEffects, [&](const BattleStackFilterEffect & filter)
{
if (!filter.persistent)
{
if (filter.source && !filter.target->hasBonus(Selector::source(Bonus::SPELL_EFFECT, filter.source->id), Selector::all))
return true;
if (filter.effect == ColorFilter::genEmptyShifter())
return true;
}
return false;
});
}
void BattleStacksController::updateHoveredStacks()
{
auto newStacks = selectHoveredStacks();
for (auto const * stack : mouseHoveredStacks)
{
if (vstd::contains(newStacks, stack))
continue;
if (stack == activeStack)
stackAnimation[stack->ID]->setBorderColor(AnimationControls::getGoldBorder());
else
stackAnimation[stack->ID]->setBorderColor(AnimationControls::getNoBorder());
}
for (auto const * stack : newStacks)
{
if (vstd::contains(mouseHoveredStacks, stack))
continue;
stackAnimation[stack->ID]->setBorderColor(AnimationControls::getBlueBorder());
if (stackAnimation[stack->ID]->framesInGroup(ECreatureAnimType::MOUSEON) > 0 && stack->alive() && !stack->isFrozen())
stackAnimation[stack->ID]->playOnce(ECreatureAnimType::MOUSEON);
}
mouseHoveredStacks = newStacks;
}
std::vector<const CStack *> BattleStacksController::selectHoveredStacks()
{
// only allow during our turn - do not try to highlight creatures while they are in the middle of actions
if (!activeStack)
return {};
if(owner.getAnimationCondition(EAnimationEvents::ACTION) == true)
return {};
auto hoveredHex = owner.fieldController->getHoveredHex();
if (!hoveredHex.isValid())
return {};
const spells::Caster *caster = nullptr;
const CSpell *spell = nullptr;
spells::Mode mode = spells::Mode::HERO;
if(owner.actionsController->spellcastingModeActive())//hero casts spell
{
spell = owner.actionsController->selectedSpell().toSpell();
caster = owner.getActiveHero();
}
else if(owner.stacksController->activeStackSpellToCast() != SpellID::NONE)//stack casts spell
{
spell = SpellID(owner.stacksController->activeStackSpellToCast()).toSpell();
caster = owner.stacksController->getActiveStack();
mode = spells::Mode::CREATURE_ACTIVE;
}
if(caster && spell) //when casting spell
{
spells::Target target;
target.emplace_back(hoveredHex);
spells::BattleCast event(owner.curInt->cb.get(), caster, mode, spell);
auto mechanics = spell->battleMechanics(&event);
return mechanics->getAffectedStacks(target);
}
if(hoveredHex.isValid())
{
const CStack * const stack = owner.curInt->cb->battleGetStackByPos(hoveredHex, true);
if (stack)
return {stack};
}
return {};
}

View File

@ -10,26 +10,38 @@
#pragma once
#include "../gui/Geometries.h"
#include "../gui/ColorFilter.h"
VCMI_LIB_NAMESPACE_BEGIN
struct BattleHex;
class BattleAction;
class CStack;
class CSpell;
class SpellID;
VCMI_LIB_NAMESPACE_END
struct StackAttackedInfo;
struct StackAttackInfo;
class ColorFilter;
class Canvas;
class BattleInterface;
class CBattleAnimation;
class BattleAnimation;
class CreatureAnimation;
class CBattleAnimation;
class BattleAnimation;
class BattleRenderer;
class IImage;
struct BattleStackFilterEffect
{
ColorFilter effect;
const CStack * target;
const CSpell * source;
bool persistent;
};
/// Class responsible for handling stacks in battle
/// Handles ordering of stacks animation
/// As well as rendering of stacks, their amount boxes
@ -44,7 +56,10 @@ class BattleStacksController
std::shared_ptr<IImage> amountEffNeutral;
/// currently displayed animations <anim, initialized>
std::vector<CBattleAnimation *> currentAnimations;
std::vector<BattleAnimation *> currentAnimations;
/// currently active color effects on stacks, in order of their addition (which corresponds to their apply order)
std::vector<BattleStackFilterEffect> stackFilterEffects;
/// animations of creatures from fighting armies (order by BattleInfo's stacks' ID)
std::map<int32_t, std::shared_ptr<CreatureAnimation>> stackAnimation;
@ -52,11 +67,11 @@ class BattleStacksController
/// <creatureID, if false reverse creature's animation> //TODO: move it to battle callback
std::map<int, bool> stackFacingRight;
/// number of active stack; nullptr - no one
/// currently active stack; nullptr - no one
const CStack *activeStack;
/// stack below mouse pointer, used for border animation
const CStack *mouseHoveredStack;
/// stacks below mouse pointer (multiple stacks possible while spellcasting), used for border animation
std::vector<const CStack *> mouseHoveredStacks;
///when animation is playing, we should wait till the end to make the next stack active; nullptr of none
const CStack *stackToActivate;
@ -77,6 +92,19 @@ class BattleStacksController
std::shared_ptr<IImage> getStackAmountBox(const CStack * stack);
void executeAttackAnimations();
void removeExpiredColorFilters();
void initializeBattleAnimations();
void stepFrameBattleAnimations();
void updateBattleAnimations();
void updateHoveredStacks();
std::vector<const CStack *> selectHoveredStacks();
bool shouldAttackFacingRight(const CStack * attacker, const CStack * defender);
public:
BattleStacksController(BattleInterface & owner);
@ -84,12 +112,13 @@ public:
bool facingRight(const CStack * stack) const;
void stackReset(const CStack * stack);
void stackAdded(const CStack * stack); //new stack appeared on battlefield
void stackAdded(const CStack * stack, bool instant); //new stack appeared on battlefield
void stackRemoved(uint32_t stackID); //stack disappeared from batlefiled
void stackActivated(const CStack *stack); //active stack has been changed
void stackMoved(const CStack *stack, std::vector<BattleHex> destHex, int distance); //stack with id number moved to destHex
void stackTeleported(const CStack *stack, std::vector<BattleHex> destHex, int distance); //stack with id number moved to destHex
void stacksAreAttacked(std::vector<StackAttackedInfo> attackedInfos); //called when a certain amount of stacks has been attacked
void stackAttacking(const CStack *attacker, BattleHex dest, const CStack *attacked, bool shooting); //called when stack with id ID is attacking something on hex dest
void stackAttacking(const StackAttackInfo & info); //called when stack with id ID is attacking something on hex dest
void startAction(const BattleAction* action);
void endAction(const BattleAction* action);
@ -100,7 +129,6 @@ public:
void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all
void setActiveStack(const CStack *stack);
void setHoveredStack(const CStack *stack);
void setSelectedStack(const CStack *stack);
void showAliveStack(Canvas & canvas, const CStack * stack);
@ -108,14 +136,19 @@ public:
void collectRenderableObjects(BattleRenderer & renderer);
void addNewAnim(CBattleAnimation *anim); //adds new anim to pendingAnims
void updateBattleAnimations();
/// Adds new color filter effect targeting stack
/// Effect will last as long as stack is affected by specified spell (unless effect is persistent)
/// If effect from same (target, source) already exists, it will be updated
void setStackColorFilter(const ColorFilter & effect, const CStack * target, const CSpell *source, bool persistent);
void addNewAnim(BattleAnimation *anim); //adds new anim to pendingAnims
const CStack* getActiveStack() const;
const CStack* getSelectedStack() const;
void update();
/// returns position of animation needed to place stack in specific hex
Point getStackPositionAtHex(BattleHex hexNum, const CStack * creature) const;
friend class CBattleAnimation; // for exposing pendingAnims/creAnims/creDir to animations
friend class BattleAnimation; // for exposing pendingAnims/creAnims/creDir to animations
};

View File

@ -1,5 +1,5 @@
/*
* BattleControlPanel.cpp, part of VCMI engine
* BattleWindow.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
@ -8,19 +8,24 @@
*
*/
#include "StdInc.h"
#include "BattleControlPanel.h"
#include "BattleWindow.h"
#include "BattleInterface.h"
#include "BattleInterfaceClasses.h"
#include "BattleFieldController.h"
#include "BattleStacksController.h"
#include "BattleActionsController.h"
#include "../CGameInfo.h"
#include "../CMessage.h"
#include "../CPlayerInterface.h"
#include "../CMusicHandler.h"
#include "../gui/Canvas.h"
#include "../gui/CCursorHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/CAnimation.h"
#include "../windows/CSpellWindow.h"
#include "../widgets/AdventureMapClasses.h"
#include "../widgets/Buttons.h"
#include "../widgets/Images.h"
@ -31,97 +36,208 @@
#include "../../lib/CConfigHandler.h"
#include "../../lib/filesystem/ResourceID.h"
BattleControlPanel::BattleControlPanel(BattleInterface & owner, const Point & position):
BattleWindow::BattleWindow(BattleInterface & owner):
owner(owner)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
REGISTER_BUILDER("battleConsole", &BattleControlPanel::buildBattleConsole);
pos.w = 800;
pos.h = 600;
pos = center();
REGISTER_BUILDER("battleConsole", &BattleWindow::buildBattleConsole);
pos += position;
const JsonNode config(ResourceID("config/widgets/BattleWindow.json"));
const JsonNode config(ResourceID("config/widgets/battleControlPanel.json"));
addCallback("options", std::bind(&BattleControlPanel::bOptionsf, this));
addCallback("surrender", std::bind(&BattleControlPanel::bSurrenderf, this));
addCallback("flee", std::bind(&BattleControlPanel::bFleef, this));
addCallback("autofight", std::bind(&BattleControlPanel::bAutofightf, this));
addCallback("spellbook", std::bind(&BattleControlPanel::bSpellf, this));
addCallback("wait", std::bind(&BattleControlPanel::bWaitf, this));
addCallback("defence", std::bind(&BattleControlPanel::bDefencef, this));
addCallback("consoleUp", std::bind(&BattleControlPanel::bConsoleUpf, this));
addCallback("consoleDown", std::bind(&BattleControlPanel::bConsoleDownf, this));
addCallback("tacticNext", std::bind(&BattleControlPanel::bTacticNextStack, this));
addCallback("tacticEnd", std::bind(&BattleControlPanel::bTacticPhaseEnd, this));
addCallback("alternativeAction", std::bind(&BattleControlPanel::bSwitchActionf, this));
addCallback("options", std::bind(&BattleWindow::bOptionsf, this));
addCallback("surrender", std::bind(&BattleWindow::bSurrenderf, this));
addCallback("flee", std::bind(&BattleWindow::bFleef, this));
addCallback("autofight", std::bind(&BattleWindow::bAutofightf, this));
addCallback("spellbook", std::bind(&BattleWindow::bSpellf, this));
addCallback("wait", std::bind(&BattleWindow::bWaitf, this));
addCallback("defence", std::bind(&BattleWindow::bDefencef, this));
addCallback("consoleUp", std::bind(&BattleWindow::bConsoleUpf, this));
addCallback("consoleDown", std::bind(&BattleWindow::bConsoleDownf, this));
addCallback("tacticNext", std::bind(&BattleWindow::bTacticNextStack, this));
addCallback("tacticEnd", std::bind(&BattleWindow::bTacticPhaseEnd, this));
addCallback("alternativeAction", std::bind(&BattleWindow::bSwitchActionf, this));
build(config);
console = widget<BattleConsole>("console");
GH.statusbar = console;
owner.console = console;
owner.fieldController.reset( new BattleFieldController(owner));
owner.fieldController->createHeroes();
//create stack queue and adjust our own position
bool embedQueue;
std::string queueSize = settings["battle"]["queueSize"].String();
if(queueSize == "auto")
embedQueue = screen->h < 700;
else
embedQueue = screen->h < 700 || queueSize == "small";
queue = std::make_shared<StackQueue>(embedQueue, owner);
if(!embedQueue && settings["battle"]["showQueue"].Bool())
{
//re-center, taking into account stack queue position
pos.y -= queue->pos.h;
pos.h += queue->pos.h;
pos = center();
}
if ( owner.tacticsMode )
tacticPhaseStarted();
else
tacticPhaseEnded();
addUsedEvents(RCLICK | KEYBOARD);
}
std::shared_ptr<BattleConsole> BattleControlPanel::buildBattleConsole(const JsonNode & config) const
BattleWindow::~BattleWindow()
{
return std::make_shared<BattleConsole>(readRect(config["rect"]));
CPlayerInterface::battleInt = nullptr;
}
void BattleControlPanel::show(SDL_Surface * to)
std::shared_ptr<BattleConsole> BattleWindow::buildBattleConsole(const JsonNode & config) const
{
//show menu before all other elements to keep it in background
if(auto w = widget<CPicture>("menu"))
w->show(to);
CIntObject::show(to);
auto rect = readRect(config["rect"]);
auto offset = readPosition(config["imagePosition"]);
auto background = widget<CPicture>("menuBattle");
return std::make_shared<BattleConsole>(background, rect.topLeft(), offset, rect.dimensions() );
}
void BattleControlPanel::showAll(SDL_Surface * to)
void BattleWindow::hideQueue()
{
//show menu before all other elements to keep it in background
if(auto w = widget<CPicture>("menu"))
w->showAll(to);
CIntObject::showAll(to);
}
Settings showQueue = settings.write["battle"]["showQueue"];
showQueue->Bool() = false;
queue->disable();
void BattleControlPanel::tacticPhaseStarted()
{
build(variables["tacticItems"]);
if(auto w = widget<CPicture>("menu"))
if (!queue->embedded)
{
w->colorize(owner.curInt->playerID);
w->recActions &= ~(SHOWALL | UPDATE);
}
}
void BattleControlPanel::tacticPhaseEnded()
{
deleteWidget("tacticNext");
deleteWidget("tacticEnd");
build(variables["battleItems"]);
if(auto w = widget<CPicture>("menu"))
{
w->colorize(owner.curInt->playerID);
w->recActions &= ~(SHOWALL | UPDATE);
//re-center, taking into account stack queue position
pos.y += queue->pos.h;
pos.h -= queue->pos.h;
pos = center();
GH.totalRedraw();
}
}
void BattleControlPanel::bOptionsf()
void BattleWindow::showQueue()
{
Settings showQueue = settings.write["battle"]["showQueue"];
showQueue->Bool() = true;
queue->enable();
if (!queue->embedded)
{
//re-center, taking into account stack queue position
pos.y -= queue->pos.h;
pos.h += queue->pos.h;
pos = center();
GH.totalRedraw();
}
}
void BattleWindow::updateQueue()
{
queue->update();
}
void BattleWindow::activate()
{
GH.statusbar = console;
CIntObject::activate();
LOCPLINT->cingconsole->activate();
}
void BattleWindow::deactivate()
{
CIntObject::deactivate();
LOCPLINT->cingconsole->deactivate();
}
void BattleWindow::keyPressed(const SDL_KeyboardEvent & key)
{
if(key.keysym.sym == SDLK_q && key.state == SDL_PRESSED)
{
if(settings["battle"]["showQueue"].Bool()) //hide queue
hideQueue();
else
showQueue();
}
else if(key.keysym.sym == SDLK_f && key.state == SDL_PRESSED)
{
owner.actionsController->enterCreatureCastingMode();
}
else if(key.keysym.sym == SDLK_ESCAPE)
{
if(owner.getAnimationCondition(EAnimationEvents::OPENING) == true)
CCS->soundh->stopSound(owner.battleIntroSoundChannel);
else
owner.actionsController->endCastingSpell();
}
}
void BattleWindow::clickRight(tribool down, bool previousState)
{
if (!down)
owner.actionsController->endCastingSpell();
}
void BattleWindow::tacticPhaseStarted()
{
auto menuBattle = widget<CIntObject>("menuBattle");
auto console = widget<CIntObject>("console");
auto menuTactics = widget<CIntObject>("menuTactics");
auto tacticNext = widget<CIntObject>("tacticNext");
auto tacticEnd = widget<CIntObject>("tacticEnd");
menuBattle->disable();
console->disable();
menuTactics->enable();
tacticNext->enable();
tacticEnd->enable();
redraw();
}
void BattleWindow::tacticPhaseEnded()
{
auto menuBattle = widget<CIntObject>("menuBattle");
auto console = widget<CIntObject>("console");
auto menuTactics = widget<CIntObject>("menuTactics");
auto tacticNext = widget<CIntObject>("tacticNext");
auto tacticEnd = widget<CIntObject>("tacticEnd");
menuBattle->enable();
console->enable();
menuTactics->disable();
tacticNext->disable();
tacticEnd->disable();
redraw();
}
void BattleWindow::bOptionsf()
{
if (owner.actionsController->spellcastingModeActive())
return;
CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
CCS->curh->set(Cursor::Map::POINTER);
GH.pushIntT<BattleOptionsWindow>(owner);
}
void BattleControlPanel::bSurrenderf()
void BattleWindow::bSurrenderf()
{
if (owner.actionsController->spellcastingModeActive())
return;
@ -141,14 +257,14 @@ void BattleControlPanel::bSurrenderf()
}
}
void BattleControlPanel::bFleef()
void BattleWindow::bFleef()
{
if (owner.actionsController->spellcastingModeActive())
return;
if ( owner.curInt->cb->battleCanFlee() )
{
CFunctionList<void()> ony = std::bind(&BattleControlPanel::reallyFlee,this);
CFunctionList<void()> ony = std::bind(&BattleWindow::reallyFlee,this);
owner.curInt->showYesNoDialog(CGI->generaltexth->allTexts[28], ony, nullptr); //Are you sure you want to retreat?
}
else
@ -170,13 +286,13 @@ void BattleControlPanel::bFleef()
}
}
void BattleControlPanel::reallyFlee()
void BattleWindow::reallyFlee()
{
owner.giveCommand(EActionType::RETREAT);
CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
CCS->curh->set(Cursor::Map::POINTER);
}
void BattleControlPanel::reallySurrender()
void BattleWindow::reallySurrender()
{
if (owner.curInt->cb->getResourceAmount(Res::GOLD) < owner.curInt->cb->battleGetSurrenderCost())
{
@ -185,11 +301,11 @@ void BattleControlPanel::reallySurrender()
else
{
owner.giveCommand(EActionType::SURRENDER);
CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
CCS->curh->set(Cursor::Map::POINTER);
}
}
void BattleControlPanel::showAlternativeActionIcon(PossiblePlayerBattleAction action)
void BattleWindow::showAlternativeActionIcon(PossiblePlayerBattleAction action)
{
auto w = widget<CButton>("alternativeAction");
if(!w)
@ -228,7 +344,7 @@ void BattleControlPanel::showAlternativeActionIcon(PossiblePlayerBattleAction ac
w->setImage(anim, false);
}
void BattleControlPanel::setAlternativeActions(const std::list<PossiblePlayerBattleAction> & actions)
void BattleWindow::setAlternativeActions(const std::list<PossiblePlayerBattleAction> & actions)
{
alternativeActions = actions;
defaultAction = PossiblePlayerBattleAction::INVALID;
@ -240,7 +356,7 @@ void BattleControlPanel::setAlternativeActions(const std::list<PossiblePlayerBat
showAlternativeActionIcon(defaultAction);
}
void BattleControlPanel::bAutofightf()
void BattleWindow::bAutofightf()
{
if (owner.actionsController->spellcastingModeActive())
return;
@ -267,7 +383,7 @@ void BattleControlPanel::bAutofightf()
}
}
void BattleControlPanel::bSpellf()
void BattleWindow::bSpellf()
{
if (owner.actionsController->spellcastingModeActive())
return;
@ -279,7 +395,7 @@ void BattleControlPanel::bSpellf()
if(!myHero)
return;
CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
CCS->curh->set(Cursor::Map::POINTER);
ESpellCastProblem::ESpellCastProblem spellCastProblem = owner.curInt->cb->battleCanCastSpell(myHero, spells::Mode::HERO);
@ -309,7 +425,7 @@ void BattleControlPanel::bSpellf()
}
}
void BattleControlPanel::bSwitchActionf()
void BattleWindow::bSwitchActionf()
{
if(alternativeActions.empty())
return;
@ -336,7 +452,7 @@ void BattleControlPanel::bSwitchActionf()
alternativeActions.pop_front();
}
void BattleControlPanel::bWaitf()
void BattleWindow::bWaitf()
{
if (owner.actionsController->spellcastingModeActive())
return;
@ -345,7 +461,7 @@ void BattleControlPanel::bWaitf()
owner.giveCommand(EActionType::WAIT);
}
void BattleControlPanel::bDefencef()
void BattleWindow::bDefencef()
{
if (owner.actionsController->spellcastingModeActive())
return;
@ -354,7 +470,7 @@ void BattleControlPanel::bDefencef()
owner.giveCommand(EActionType::DEFEND);
}
void BattleControlPanel::bConsoleUpf()
void BattleWindow::bConsoleUpf()
{
if (owner.actionsController->spellcastingModeActive())
return;
@ -362,7 +478,7 @@ void BattleControlPanel::bConsoleUpf()
console->scrollUp();
}
void BattleControlPanel::bConsoleDownf()
void BattleWindow::bConsoleDownf()
{
if (owner.actionsController->spellcastingModeActive())
return;
@ -370,17 +486,17 @@ void BattleControlPanel::bConsoleDownf()
console->scrollDown();
}
void BattleControlPanel::bTacticNextStack()
void BattleWindow::bTacticNextStack()
{
owner.tacticNextStack(nullptr);
}
void BattleControlPanel::bTacticPhaseEnd()
void BattleWindow::bTacticPhaseEnd()
{
owner.tacticPhaseEnd();
}
void BattleControlPanel::blockUI(bool on)
void BattleWindow::blockUI(bool on)
{
bool canCastSpells = false;
auto hero = owner.curInt->cb->battleGetMyHero();
@ -433,3 +549,24 @@ void BattleControlPanel::blockUI(bool on)
}
}
}
void BattleWindow::showAll(SDL_Surface *to)
{
CIntObject::showAll(to);
if (screen->w != 800 || screen->h !=600)
CMessage::drawBorder(owner.curInt->playerID, to, pos.w+28, pos.h+29, pos.x-14, pos.y-15);
}
void BattleWindow::show(SDL_Surface *to)
{
CIntObject::show(to);
LOCPLINT->cingconsole->show(to);
}
void BattleWindow::close()
{
if(GH.topInt().get() != this)
logGlobal->error("Only top interface must be closed");
GH.popInts(1);
}

View File

@ -1,5 +1,5 @@
/*
* BattleControlPanel.h, part of VCMI engine
* BattleWindow.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
@ -21,13 +21,16 @@ VCMI_LIB_NAMESPACE_END
class CButton;
class BattleInterface;
class BattleConsole;
class BattleRenderer;
class StackQueue;
/// GUI object that handles functionality of panel at the bottom of combat screen
class BattleControlPanel : public InterfaceObjectConfigurable
{
class BattleWindow : public InterfaceObjectConfigurable
{
BattleInterface & owner;
std::shared_ptr<BattleConsole> buildBattleConsole(const JsonNode &) const;
std::shared_ptr<StackQueue> queue;
std::shared_ptr<BattleConsole> console;
/// button press handling functions
void bOptionsf();
@ -52,24 +55,40 @@ class BattleControlPanel : public InterfaceObjectConfigurable
PossiblePlayerBattleAction defaultAction;
void showAlternativeActionIcon(PossiblePlayerBattleAction);
/// Toggle StackQueue visibility
void hideQueue();
void showQueue();
std::shared_ptr<BattleConsole> buildBattleConsole(const JsonNode &) const;
public:
std::shared_ptr<BattleConsole> console;
BattleWindow(BattleInterface & owner );
~BattleWindow();
/// Closes window once battle finished
void close();
/// block all UI elements when player is not allowed to act, e.g. during enemy turn
void blockUI(bool on);
void show(SDL_Surface * to) override;
void showAll(SDL_Surface * to) override;
/// Refresh queue after turn order changes
void updateQueue();
void activate() override;
void deactivate() override;
void keyPressed(const SDL_KeyboardEvent & key) override;
void clickRight(tribool down, bool previousState) override;
void show(SDL_Surface *to) override;
void showAll(SDL_Surface *to) override;
/// Toggle UI to displaying tactics phase
void tacticPhaseStarted();
/// Toggle UI to displaying battle log in place of tactics UI
void tacticPhaseEnded();
/// Set possible alternative options. If more than 1 - the last will be considered as default option
void setAlternativeActions(const std::list<PossiblePlayerBattleAction> &);
BattleControlPanel(BattleInterface & owner, const Point & position);
};

View File

@ -14,11 +14,17 @@
#include "../../lib/CCreatureHandler.h"
#include "../gui/Canvas.h"
#include "../gui/ColorFilter.h"
static const SDL_Color creatureBlueBorder = { 0, 255, 255, 255 };
static const SDL_Color creatureGoldBorder = { 255, 255, 0, 255 };
static const SDL_Color creatureNoBorder = { 0, 0, 0, 0 };
static SDL_Color genShadow(ui8 alpha)
{
return CSDL_Ext::makeColor(0, 0, 0, alpha);
}
SDL_Color AnimationControls::getBlueBorder()
{
return creatureBlueBorder;
@ -40,10 +46,8 @@ std::shared_ptr<CreatureAnimation> AnimationControls::getAnimation(const CCreatu
return std::make_shared<CreatureAnimation>(creature->animDefName, func);
}
float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, const CreatureAnimation * anim, size_t group)
float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, const CreatureAnimation * anim, ECreatureAnimType type)
{
CCreatureAnim::EAnimType type = CCreatureAnim::EAnimType(group);
assert(creature->animation.walkAnimationTime != 0);
assert(creature->animation.attackAnimationTime != 0);
assert(anim->framesInGroup(type) != 0);
@ -58,49 +62,50 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c
switch (type)
{
case CCreatureAnim::MOVING:
case ECreatureAnimType::MOVING:
return static_cast<float>(speed * 2 * creature->animation.walkAnimationTime / anim->framesInGroup(type));
case CCreatureAnim::MOUSEON:
case ECreatureAnimType::MOUSEON:
return baseSpeed;
case CCreatureAnim::HOLDING:
case ECreatureAnimType::HOLDING:
return static_cast<float>(baseSpeed * creature->animation.idleAnimationTime / anim->framesInGroup(type));
case CCreatureAnim::SHOOT_UP:
case CCreatureAnim::SHOOT_FRONT:
case CCreatureAnim::SHOOT_DOWN:
case CCreatureAnim::CAST_UP:
case CCreatureAnim::CAST_FRONT:
case CCreatureAnim::CAST_DOWN:
case CCreatureAnim::VCMI_CAST_DOWN:
case CCreatureAnim::VCMI_CAST_FRONT:
case CCreatureAnim::VCMI_CAST_UP:
case ECreatureAnimType::SHOOT_UP:
case ECreatureAnimType::SHOOT_FRONT:
case ECreatureAnimType::SHOOT_DOWN:
case ECreatureAnimType::SPECIAL_UP:
case ECreatureAnimType::SPECIAL_FRONT:
case ECreatureAnimType::SPECIAL_DOWN:
case ECreatureAnimType::CAST_DOWN:
case ECreatureAnimType::CAST_FRONT:
case ECreatureAnimType::CAST_UP:
return static_cast<float>(speed * 4 * creature->animation.attackAnimationTime / anim->framesInGroup(type));
// as strange as it looks like "attackAnimationTime" does not affects melee attacks
// necessary because length of these animations must be same for all creatures for synchronization
case CCreatureAnim::ATTACK_UP:
case CCreatureAnim::ATTACK_FRONT:
case CCreatureAnim::ATTACK_DOWN:
case CCreatureAnim::HITTED:
case CCreatureAnim::DEFENCE:
case CCreatureAnim::DEATH:
case CCreatureAnim::DEATH_RANGED:
case CCreatureAnim::VCMI_2HEX_DOWN:
case CCreatureAnim::VCMI_2HEX_FRONT:
case CCreatureAnim::VCMI_2HEX_UP:
case ECreatureAnimType::ATTACK_UP:
case ECreatureAnimType::ATTACK_FRONT:
case ECreatureAnimType::ATTACK_DOWN:
case ECreatureAnimType::HITTED:
case ECreatureAnimType::DEFENCE:
case ECreatureAnimType::DEATH:
case ECreatureAnimType::DEATH_RANGED:
case ECreatureAnimType::RESURRECTION:
case ECreatureAnimType::GROUP_ATTACK_DOWN:
case ECreatureAnimType::GROUP_ATTACK_FRONT:
case ECreatureAnimType::GROUP_ATTACK_UP:
return speed * 3 / anim->framesInGroup(type);
case CCreatureAnim::TURN_L:
case CCreatureAnim::TURN_R:
case ECreatureAnimType::TURN_L:
case ECreatureAnimType::TURN_R:
return speed / 3;
case CCreatureAnim::MOVE_START:
case CCreatureAnim::MOVE_END:
case ECreatureAnimType::MOVE_START:
case ECreatureAnimType::MOVE_END:
return speed / 3;
case CCreatureAnim::DEAD:
case CCreatureAnim::DEAD_RANGED:
case ECreatureAnimType::DEAD:
case ECreatureAnimType::DEAD_RANGED:
return speed;
default:
@ -110,12 +115,12 @@ float AnimationControls::getCreatureAnimationSpeed(const CCreature * creature, c
float AnimationControls::getProjectileSpeed()
{
return static_cast<float>(settings["battle"]["animationSpeed"].Float() * 100);
return static_cast<float>(settings["battle"]["animationSpeed"].Float() * 4000);
}
float AnimationControls::getCatapultSpeed()
{
return static_cast<float>(settings["battle"]["animationSpeed"].Float() * 20);
return static_cast<float>(settings["battle"]["animationSpeed"].Float() * 1000);
}
float AnimationControls::getSpellEffectSpeed()
@ -133,12 +138,22 @@ float AnimationControls::getFlightDistance(const CCreature * creature)
return static_cast<float>(creature->animation.flightAnimationDistance * 200);
}
CCreatureAnim::EAnimType CreatureAnimation::getType() const
float AnimationControls::getFadeInDuration()
{
return 1.0f / settings["battle"]["animationSpeed"].Float();
}
float AnimationControls::getObstaclesSpeed()
{
return 10.0;// does not seems to be affected by animaiton speed settings
}
ECreatureAnimType CreatureAnimation::getType() const
{
return type;
}
void CreatureAnimation::setType(CCreatureAnim::EAnimType type)
void CreatureAnimation::setType(ECreatureAnimType type)
{
this->type = type;
currentFrame = 0;
@ -147,21 +162,13 @@ void CreatureAnimation::setType(CCreatureAnim::EAnimType type)
play();
}
void CreatureAnimation::shiftColor(const ColorShifter* shifter)
{
if(forward)
forward->shiftColor(shifter);
if(reverse)
reverse->shiftColor(shifter);
}
CreatureAnimation::CreatureAnimation(const std::string & name_, TSpeedController controller)
: name(name_),
speed(0.1f),
shadowAlpha(128),
currentFrame(0),
elapsedTime(0),
type(CCreatureAnim::HOLDING),
type(ECreatureAnimType::HOLDING),
border(CSDL_Ext::makeColor(0, 0, 0, 0)),
speedController(controller),
once(false)
@ -174,20 +181,37 @@ CreatureAnimation::CreatureAnimation(const std::string & name_, TSpeedController
reverse->preload();
// if necessary, add one frame into vcmi-only group DEAD
if(forward->size(CCreatureAnim::DEAD) == 0)
if(forward->size(size_t(ECreatureAnimType::DEAD)) == 0)
{
forward->duplicateImage(CCreatureAnim::DEATH, forward->size(CCreatureAnim::DEATH)-1, CCreatureAnim::DEAD);
reverse->duplicateImage(CCreatureAnim::DEATH, reverse->size(CCreatureAnim::DEATH)-1, CCreatureAnim::DEAD);
forward->duplicateImage(size_t(ECreatureAnimType::DEATH), forward->size(size_t(ECreatureAnimType::DEATH))-1, size_t(ECreatureAnimType::DEAD));
reverse->duplicateImage(size_t(ECreatureAnimType::DEATH), reverse->size(size_t(ECreatureAnimType::DEATH))-1, size_t(ECreatureAnimType::DEAD));
}
if(forward->size(CCreatureAnim::DEAD_RANGED) == 0 && forward->size(CCreatureAnim::DEATH_RANGED) != 0)
if(forward->size(size_t(ECreatureAnimType::DEAD_RANGED)) == 0 && forward->size(size_t(ECreatureAnimType::DEATH_RANGED)) != 0)
{
forward->duplicateImage(CCreatureAnim::DEATH_RANGED, forward->size(CCreatureAnim::DEATH_RANGED)-1, CCreatureAnim::DEAD_RANGED);
reverse->duplicateImage(CCreatureAnim::DEATH_RANGED, reverse->size(CCreatureAnim::DEATH_RANGED)-1, CCreatureAnim::DEAD_RANGED);
forward->duplicateImage(size_t(ECreatureAnimType::DEATH_RANGED), forward->size(size_t(ECreatureAnimType::DEATH_RANGED))-1, size_t(ECreatureAnimType::DEAD_RANGED));
reverse->duplicateImage(size_t(ECreatureAnimType::DEATH_RANGED), reverse->size(size_t(ECreatureAnimType::DEATH_RANGED))-1, size_t(ECreatureAnimType::DEAD_RANGED));
}
if(forward->size(size_t(ECreatureAnimType::FROZEN)) == 0)
{
forward->duplicateImage(size_t(ECreatureAnimType::HOLDING), 0, size_t(ECreatureAnimType::FROZEN));
reverse->duplicateImage(size_t(ECreatureAnimType::HOLDING), 0, size_t(ECreatureAnimType::FROZEN));
}
if(forward->size(size_t(ECreatureAnimType::RESURRECTION)) == 0)
{
for (size_t i = 0; i < forward->size(size_t(ECreatureAnimType::DEATH)); ++i)
{
size_t current = forward->size(size_t(ECreatureAnimType::DEATH)) - 1 - i;
forward->duplicateImage(size_t(ECreatureAnimType::DEATH), current, size_t(ECreatureAnimType::RESURRECTION));
reverse->duplicateImage(size_t(ECreatureAnimType::DEATH), current, size_t(ECreatureAnimType::RESURRECTION));
}
}
//TODO: get dimensions form CAnimation
auto first = forward->getImage(0, type, true);
auto first = forward->getImage(0, size_t(type), true);
if(!first)
{
@ -228,7 +252,7 @@ bool CreatureAnimation::incrementFrame(float timePassed)
currentFrame -= framesNumber;
if(once)
setType(CCreatureAnim::HOLDING);
setType(ECreatureAnimType::HOLDING);
endAnimation();
return true;
@ -256,7 +280,7 @@ float CreatureAnimation::getCurrentFrame() const
return currentFrame;
}
void CreatureAnimation::playOnce( CCreatureAnim::EAnimType type )
void CreatureAnimation::playOnce( ECreatureAnimType type )
{
setType(type);
once = true;
@ -269,11 +293,6 @@ inline int getBorderStrength(float time)
return static_cast<int>(borderStrength * 155 + 100); // scale to 0-255
}
static SDL_Color genShadow(ui8 alpha)
{
return CSDL_Ext::makeColor(0, 0, 0, alpha);
}
static SDL_Color genBorderColor(ui8 alpha, const SDL_Color & base)
{
return CSDL_Ext::makeColor(base.r, base.g, base.b, ui8(base.a * alpha / 256));
@ -294,80 +313,89 @@ static SDL_Color addColors(const SDL_Color & base, const SDL_Color & over)
);
}
void CreatureAnimation::genBorderPalette(IImage::BorderPallete & target)
void CreatureAnimation::genSpecialPalette(IImage::SpecialPalette & target)
{
target[0] = genBorderColor(getBorderStrength(elapsedTime), border);
target[1] = addColors(genShadow(128), genBorderColor(getBorderStrength(elapsedTime), border));
target[2] = addColors(genShadow(64), genBorderColor(getBorderStrength(elapsedTime), border));
target[0] = genShadow(shadowAlpha / 2);
target[1] = genShadow(shadowAlpha / 2);
target[2] = genShadow(shadowAlpha);
target[3] = genShadow(shadowAlpha);
target[4] = genBorderColor(getBorderStrength(elapsedTime), border);
target[5] = addColors(genShadow(shadowAlpha), genBorderColor(getBorderStrength(elapsedTime), border));
target[6] = addColors(genShadow(shadowAlpha / 2), genBorderColor(getBorderStrength(elapsedTime), border));
}
void CreatureAnimation::nextFrame(Canvas & canvas, bool facingRight)
void CreatureAnimation::nextFrame(Canvas & canvas, const ColorFilter & shifter, bool facingRight)
{
SDL_Color shadowTest = shifter.shiftColor(genShadow(128));
shadowAlpha = shadowTest.a;
size_t frame = static_cast<size_t>(floor(currentFrame));
std::shared_ptr<IImage> image;
if(facingRight)
image = forward->getImage(frame, type);
image = forward->getImage(frame, size_t(type));
else
image = reverse->getImage(frame, type);
image = reverse->getImage(frame, size_t(type));
if(image)
{
IImage::BorderPallete borderPallete;
genBorderPalette(borderPallete);
IImage::SpecialPalette SpecialPalette;
genSpecialPalette(SpecialPalette);
image->setBorderPallete(borderPallete);
image->setSpecialPallete(SpecialPalette);
image->adjustPalette(shifter);
canvas.draw(image, pos.topLeft(), Rect(0, 0, pos.w, pos.h));
}
}
int CreatureAnimation::framesInGroup(CCreatureAnim::EAnimType group) const
int CreatureAnimation::framesInGroup(ECreatureAnimType group) const
{
return static_cast<int>(forward->size(group));
return static_cast<int>(forward->size(size_t(group)));
}
bool CreatureAnimation::isDead() const
{
return getType() == CCreatureAnim::DEAD
|| getType() == CCreatureAnim::DEAD_RANGED;
return getType() == ECreatureAnimType::DEAD
|| getType() == ECreatureAnimType::DEAD_RANGED;
}
bool CreatureAnimation::isDying() const
{
return getType() == CCreatureAnim::DEATH
|| getType() == CCreatureAnim::DEATH_RANGED;
return getType() == ECreatureAnimType::DEATH
|| getType() == ECreatureAnimType::DEATH_RANGED;
}
bool CreatureAnimation::isDeadOrDying() const
{
return getType() == CCreatureAnim::DEAD
|| getType() == CCreatureAnim::DEATH
|| getType() == CCreatureAnim::DEAD_RANGED
|| getType() == CCreatureAnim::DEATH_RANGED;
return getType() == ECreatureAnimType::DEAD
|| getType() == ECreatureAnimType::DEATH
|| getType() == ECreatureAnimType::DEAD_RANGED
|| getType() == ECreatureAnimType::DEATH_RANGED;
}
bool CreatureAnimation::isIdle() const
{
return getType() == CCreatureAnim::HOLDING
|| getType() == CCreatureAnim::MOUSEON;
return getType() == ECreatureAnimType::HOLDING
|| getType() == ECreatureAnimType::MOUSEON;
}
bool CreatureAnimation::isMoving() const
{
return getType() == CCreatureAnim::MOVE_START
|| getType() == CCreatureAnim::MOVING
|| getType() == CCreatureAnim::MOVE_END
|| getType() == CCreatureAnim::TURN_L
|| getType() == CCreatureAnim::TURN_R;
return getType() == ECreatureAnimType::MOVE_START
|| getType() == ECreatureAnimType::MOVING
|| getType() == ECreatureAnimType::MOVE_END
|| getType() == ECreatureAnimType::TURN_L
|| getType() == ECreatureAnimType::TURN_R;
}
bool CreatureAnimation::isShooting() const
{
return getType() == CCreatureAnim::SHOOT_UP
|| getType() == CCreatureAnim::SHOOT_FRONT
|| getType() == CCreatureAnim::SHOOT_DOWN;
return getType() == ECreatureAnimType::SHOOT_UP
|| getType() == ECreatureAnimType::SHOOT_FRONT
|| getType() == ECreatureAnimType::SHOOT_DOWN;
}
void CreatureAnimation::pause()
@ -379,6 +407,6 @@ void CreatureAnimation::play()
{
//logAnim->trace("Play %s group %d at %d:%d", name, static_cast<int>(getType()), pos.x, pos.y);
speed = 0;
if(speedController(this, type) != 0)
speed = 1 / speedController(this, type);
if(speedController(this, type) != 0)
speed = 1 / speedController(this, type);
}

View File

@ -29,13 +29,12 @@ namespace AnimationControls
std::shared_ptr<CreatureAnimation> getAnimation(const CCreature * creature);
/// returns animation speed of specific group, taking in mind game setting (in frames per second)
float getCreatureAnimationSpeed(const CCreature * creature, const CreatureAnimation * anim, size_t groupID);
float getCreatureAnimationSpeed(const CCreature * creature, const CreatureAnimation * anim, ECreatureAnimType groupID);
/// returns how far projectile should move each frame
/// TODO: make it time-based
/// returns how far projectile should move per second
float getProjectileSpeed();
/// returns speed of catapult projectile
/// returns speed of catapult projectile, in pixels per second (horizontal axis only)
float getCatapultSpeed();
/// returns speed of any spell effects, including any special effects like morale (in frames per second)
@ -46,6 +45,12 @@ namespace AnimationControls
/// Returns distance on which flying creatures should during one animation loop
float getFlightDistance(const CCreature * creature);
/// Returns total time for full fade-in effect on newly summoned creatures, in seconds
float getFadeInDuration();
/// Returns animation speed for obstacles, in frames per second
float getObstaclesSpeed();
}
/// Class which manages animations of creatures/units inside battles
@ -53,7 +58,7 @@ namespace AnimationControls
class CreatureAnimation : public CIntObject
{
public:
typedef std::function<float(CreatureAnimation *, size_t)> TSpeedController;
typedef std::function<float(CreatureAnimation *, ECreatureAnimType)> TSpeedController;
private:
std::string name;
@ -78,7 +83,10 @@ private:
float elapsedTime;
///type of animation being displayed
CCreatureAnim::EAnimType type;
ECreatureAnimType type;
/// current value of shadow transparency
uint8_t shadowAlpha;
/// border color, disabled if alpha = 0
SDL_Color border;
@ -90,7 +98,7 @@ private:
void endAnimation();
void genBorderPalette(IImage::BorderPallete & target);
void genSpecialPalette(IImage::SpecialPalette & target);
public:
/// function(s) that will be called when animation ends, after reset to 1st frame
@ -107,29 +115,26 @@ public:
CreatureAnimation(const std::string & name_, TSpeedController speedController);
/// sets type of animation and resets framecount
void setType(CCreatureAnim::EAnimType type);
void setType(ECreatureAnimType type);
/// returns currently rendered type of animation
CCreatureAnim::EAnimType getType() const;
ECreatureAnimType getType() const;
void nextFrame(Canvas & canvas, bool facingRight);
void nextFrame(Canvas & canvas, const ColorFilter & shifter, bool facingRight);
/// should be called every frame, return true when animation was reset to beginning
bool incrementFrame(float timePassed);
void setBorderColor(SDL_Color palette);
/// apply color tint effect
void shiftColor(const ColorShifter * shifter);
/// Gets the current frame ID within current group.
float getCurrentFrame() const;
/// plays once given type of animation, then resets to idle
void playOnce(CCreatureAnim::EAnimType type);
void playOnce(ECreatureAnimType type);
/// returns number of frames in selected animation type
int framesInGroup(CCreatureAnim::EAnimType type) const;
int framesInGroup(ECreatureAnimType group) const;
void pause();
void play();

View File

@ -12,6 +12,7 @@
#include "SDL_Extensions.h"
#include "SDL_Pixels.h"
#include "ColorFilter.h"
#include "../CBitmapHandler.h"
#include "../Graphics.h"
@ -33,6 +34,7 @@ class CDefFile
{
private:
PACKED_STRUCT_BEGIN
struct SSpriteDef
{
ui32 size;
@ -43,7 +45,7 @@ private:
ui32 height;
si32 leftMargin;
si32 topMargin;
} PACKED_STRUCT;
} PACKED_STRUCT_END;
//offset[group][frame] - offset of frame data in file
std::map<size_t, std::vector <size_t> > offset;
@ -105,10 +107,11 @@ public:
void verticalFlip() override;
void shiftPalette(int from, int howMany) override;
void adjustPalette(const ColorShifter * shifter) override;
void adjustPalette(const ColorFilter & shifter) override;
void resetPalette(int colorID) override;
void resetPalette() override;
void setBorderPallete(const BorderPallete & borderPallete) override;
void setSpecialPallete(const SpecialPalette & SpecialPalette) override;
friend class SDLImageLoader;
@ -212,32 +215,17 @@ CDefFile::CDefFile(std::string Name):
data(nullptr),
palette(nullptr)
{
#if 0
static SDL_Color H3_ORIG_PALETTE[8] =
{
{ 0, 255, 255, SDL_ALPHA_OPAQUE},
{255, 150, 255, SDL_ALPHA_OPAQUE},
{255, 100, 255, SDL_ALPHA_OPAQUE},
{255, 50, 255, SDL_ALPHA_OPAQUE},
{255, 0, 255, SDL_ALPHA_OPAQUE},
{255, 255, 0, SDL_ALPHA_OPAQUE},
{180, 0, 255, SDL_ALPHA_OPAQUE},
{ 0, 255, 0, SDL_ALPHA_OPAQUE}
};
#endif // 0
//First 8 colors in def palette used for transparency
static SDL_Color H3Palette[8] =
{
{ 0, 0, 0, 0},// 100% - transparency
{ 0, 0, 0, 32},// 75% - shadow border,
{ 0, 0, 0, 64},// TODO: find exact value
{ 0, 0, 0, 128},// TODO: for transparency
{ 0, 0, 0, 128},// 50% - shadow body
{ 0, 0, 0, 0},// 100% - selection highlight
{ 0, 0, 0, 128},// 50% - shadow body below selection
{ 0, 0, 0, 64} // 75% - shadow border below selection
{ 0, 0, 0, 0},// transparency ( used in most images )
{ 0, 0, 0, 64},// shadow border ( used in battle, adventure map def's )
{ 0, 0, 0, 64},// shadow border ( used in fog-of-war def's )
{ 0, 0, 0, 128},// shadow body ( used in fog-of-war def's )
{ 0, 0, 0, 128},// shadow body ( used in battle, adventure map def's )
{ 0, 0, 0, 0},// selection ( used in battle def's )
{ 0, 0, 0, 128},// shadow body below selection ( used in battle def's )
{ 0, 0, 0, 64} // shadow border below selection ( used in battle def's )
};
data = animationCache.getCachedFile(ResourceID(std::string("SPRITES/") + Name, EResType::ANIMATION));
@ -804,7 +792,7 @@ void SDLImage::shiftPalette(int from, int howMany)
}
}
void SDLImage::adjustPalette(const ColorShifter * shifter)
void SDLImage::adjustPalette(const ColorFilter & shifter)
{
if(originalPalette == nullptr)
return;
@ -814,7 +802,7 @@ void SDLImage::adjustPalette(const ColorShifter * shifter)
// Note: here we skip the first 8 colors in the palette that predefined in H3Palette
for(int i = 8; i < palette->ncolors; i++)
{
palette->colors[i] = shifter->shiftColor(originalPalette->colors[i]);
palette->colors[i] = shifter.shiftColor(originalPalette->colors[i]);
}
}
@ -827,11 +815,20 @@ void SDLImage::resetPalette()
SDL_SetPaletteColors(surf->format->palette, originalPalette->colors, 0, originalPalette->ncolors);
}
void SDLImage::setBorderPallete(const IImage::BorderPallete & borderPallete)
void SDLImage::resetPalette( int colorID )
{
if(originalPalette == nullptr)
return;
// Always keept the original palette not changed, copy a new palette to assign to surface
SDL_SetPaletteColors(surf->format->palette, originalPalette->colors + colorID, colorID, 1);
}
void SDLImage::setSpecialPallete(const IImage::SpecialPalette & SpecialPalette)
{
if(surf->format->palette)
{
SDL_SetColors(surf, const_cast<SDL_Color *>(borderPallete.data()), 5, 3);
SDL_SetColors(surf, const_cast<SDL_Color *>(SpecialPalette.data()), 1, 7);
}
}
@ -1094,18 +1091,6 @@ void CAnimation::duplicateImage(const size_t sourceGroup, const size_t sourceFra
load(index, targetGroup);
}
void CAnimation::shiftColor(const ColorShifter * shifter)
{
for(auto groupIter = images.begin(); groupIter != images.end(); groupIter++)
{
for(auto frameIter = groupIter->second.begin(); frameIter != groupIter->second.end(); frameIter++)
{
std::shared_ptr<IImage> image = frameIter->second;
image->adjustPalette(shifter);
}
}
}
void CAnimation::setCustom(std::string filename, size_t frame, size_t group)
{
if (source[group].size() <= frame)

View File

@ -29,7 +29,7 @@ VCMI_LIB_NAMESPACE_END
struct SDL_Surface;
class CDefFile;
class ColorShifter;
class ColorFilter;
/*
* Base class for images, can be used for non-animation pictures as well
@ -37,7 +37,7 @@ class ColorShifter;
class IImage
{
public:
using BorderPallete = std::array<SDL_Color, 3>;
using SpecialPalette = std::array<SDL_Color, 7>;
//draws image on surface "where" at position
virtual void draw(SDL_Surface * where, int posX = 0, int posY = 0, const Rect * src = nullptr, ui8 alpha = 255) const=0;
@ -62,11 +62,12 @@ public:
//only indexed bitmaps, 16 colors maximum
virtual void shiftPalette(int from, int howMany) = 0;
virtual void adjustPalette(const ColorShifter * shifter) = 0;
virtual void adjustPalette(const ColorFilter & shifter) = 0;
virtual void resetPalette(int colorID) = 0;
virtual void resetPalette() = 0;
//only indexed bitmaps, colors 5,6,7 must be special
virtual void setBorderPallete(const BorderPallete & borderPallete) = 0;
//only indexed bitmaps with 7 special colors
virtual void setSpecialPallete(const SpecialPalette & SpecialPalette) = 0;
virtual void horizontalFlip() = 0;
virtual void verticalFlip() = 0;
@ -121,9 +122,6 @@ public:
//and loads it if animation is preloaded
void duplicateImage(const size_t sourceGroup, const size_t sourceFrame, const size_t targetGroup);
// adjust the color of the animation, used in battle spell effects, e.g. Cloned objects
void shiftColor(const ColorShifter * shifter);
//add custom surface to the selected position.
void setCustom(std::string filename, size_t frame, size_t group=0);

View File

@ -38,13 +38,18 @@ void CCursorHandler::replaceBuffer(CIntObject * payload)
updateBuffer(payload);
}
void CCursorHandler::initCursor()
CCursorHandler::CCursorHandler()
: needUpdate(true)
, buffer(nullptr)
, cursorLayer(nullptr)
, frameTime(0.f)
, showing(false)
{
cursorLayer = SDL_CreateTexture(mainRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, 40, 40);
SDL_SetTextureBlendMode(cursorLayer, SDL_BLENDMODE_BLEND);
xpos = ypos = 0;
type = ECursor::DEFAULT;
type = Cursor::Type::DEFAULT;
dndObject = nullptr;
cursors =
@ -55,17 +60,22 @@ void CCursorHandler::initCursor()
std::make_unique<CAnimImage>("CRSPELL", 0)
};
currentCursor = cursors.at(int(ECursor::DEFAULT)).get();
currentCursor = cursors.at(static_cast<size_t>(Cursor::Type::DEFAULT)).get();
buffer = CSDL_Ext::newSurface(40,40);
SDL_SetSurfaceBlendMode(buffer, SDL_BLENDMODE_NONE);
SDL_ShowCursor(SDL_DISABLE);
changeGraphic(ECursor::ADVENTURE, 0);
set(Cursor::Map::POINTER);
}
void CCursorHandler::changeGraphic(ECursor::ECursorTypes type, int index)
Point CCursorHandler::position() const
{
return Point(xpos, ypos);
}
void CCursorHandler::changeGraphic(Cursor::Type type, size_t index)
{
assert(dndObject == nullptr);
@ -73,7 +83,7 @@ void CCursorHandler::changeGraphic(ECursor::ECursorTypes type, int index)
{
this->type = type;
this->frame = index;
currentCursor = cursors.at(int(type)).get();
currentCursor = cursors.at(static_cast<size_t>(type)).get();
currentCursor->setFrame(index);
}
else if(index != this->frame)
@ -85,6 +95,27 @@ void CCursorHandler::changeGraphic(ECursor::ECursorTypes type, int index)
replaceBuffer(currentCursor);
}
void CCursorHandler::set(Cursor::Default index)
{
changeGraphic(Cursor::Type::DEFAULT, static_cast<size_t>(index));
}
void CCursorHandler::set(Cursor::Map index)
{
changeGraphic(Cursor::Type::ADVENTURE, static_cast<size_t>(index));
}
void CCursorHandler::set(Cursor::Combat index)
{
changeGraphic(Cursor::Type::COMBAT, static_cast<size_t>(index));
}
void CCursorHandler::set(Cursor::Spellcast index)
{
//Note: this is animated cursor, ignore specified frame and only change type
changeGraphic(Cursor::Type::SPELLBOOK, frame);
}
void CCursorHandler::dragAndDropCursor(std::unique_ptr<CAnimImage> object)
{
dndObject = std::move(object);
@ -102,54 +133,57 @@ void CCursorHandler::cursorMove(const int & x, const int & y)
void CCursorHandler::shiftPos( int &x, int &y )
{
if(( type == ECursor::COMBAT && frame != ECursor::COMBAT_POINTER) || type == ECursor::SPELLBOOK)
if(( type == Cursor::Type::COMBAT && frame != static_cast<size_t>(Cursor::Combat::POINTER)) || type == Cursor::Type::SPELLBOOK)
{
x-=16;
y-=16;
// Properly align the melee attack cursors.
if (type == ECursor::COMBAT)
if (type == Cursor::Type::COMBAT)
{
switch (frame)
switch (static_cast<Cursor::Combat>(frame))
{
case 7: // Bottom left
case Cursor::Combat::HIT_NORTHEAST:
x -= 6;
y += 16;
break;
case 8: // Left
case Cursor::Combat::HIT_EAST:
x -= 16;
y += 10;
break;
case 9: // Top left
case Cursor::Combat::HIT_SOUTHEAST:
x -= 6;
y -= 6;
break;
case 10: // Top right
case Cursor::Combat::HIT_SOUTHWEST:
x += 16;
y -= 6;
break;
case 11: // Right
case Cursor::Combat::HIT_WEST:
x += 16;
y += 11;
break;
case 12: // Bottom right
case Cursor::Combat::HIT_NORTHWEST:
x += 16;
y += 16;
break;
case 13: // Below
case Cursor::Combat::HIT_NORTH:
x += 9;
y += 16;
break;
case 14: // Above
case Cursor::Combat::HIT_SOUTH:
x += 9;
y -= 15;
break;
}
}
}
else if(type == ECursor::ADVENTURE)
else if(type == Cursor::Type::ADVENTURE)
{
if (frame == 0); //to exclude
if (frame == 0)
{
//no-op
}
else if(frame == 2)
{
x -= 12;
@ -221,6 +255,27 @@ void CCursorHandler::render()
if(!showing)
return;
if (type == Cursor::Type::SPELLBOOK)
{
static const float frameDisplayDuration = 0.1f;
frameTime += GH.mainFPSmng->getElapsedMilliseconds() / 1000.f;
size_t newFrame = frame;
while (frameTime > frameDisplayDuration)
{
frameTime -= frameDisplayDuration;
newFrame++;
}
auto & animation = cursors.at(static_cast<size_t>(type));
while (newFrame > animation->size())
newFrame -= animation->size();
changeGraphic(Cursor::Type::SPELLBOOK, newFrame);
}
//the must update texture in the main (renderer) thread, but changes to cursor type may come from other threads
updateTexture();
@ -252,15 +307,6 @@ void CCursorHandler::updateTexture()
}
}
CCursorHandler::CCursorHandler()
: needUpdate(true),
buffer(nullptr),
cursorLayer(nullptr),
showing(false)
{
}
CCursorHandler::~CCursorHandler()
{
if(buffer)

View File

@ -12,16 +12,97 @@ class CIntObject;
class CAnimImage;
struct SDL_Surface;
struct SDL_Texture;
struct Point;
namespace ECursor
namespace Cursor
{
enum ECursorTypes { ADVENTURE, COMBAT, DEFAULT, SPELLBOOK };
enum class Type {
ADVENTURE, // set of various cursors for adventure map
COMBAT, // set of various cursors for combat
DEFAULT, // default arrow and hourglass cursors
SPELLBOOK // animated cursor for spellcasting
};
enum EBattleCursors { COMBAT_BLOCKED, COMBAT_MOVE, COMBAT_FLY, COMBAT_SHOOT,
COMBAT_HERO, COMBAT_QUERY, COMBAT_POINTER,
//various attack frames
COMBAT_SHOOT_PENALTY = 15, COMBAT_SHOOT_CATAPULT, COMBAT_HEAL,
COMBAT_SACRIFICE, COMBAT_TELEPORT};
enum class Default {
POINTER = 0,
//ARROW_COPY = 1, // probably unused
HOURGLASS = 2,
};
enum class Combat {
INVALID = -1,
BLOCKED = 0,
MOVE = 1,
FLY = 2,
SHOOT = 3,
HERO = 4,
QUERY = 5,
POINTER = 6,
HIT_NORTHEAST = 7,
HIT_EAST = 8,
HIT_SOUTHEAST = 9,
HIT_SOUTHWEST = 10,
HIT_WEST = 11,
HIT_NORTHWEST = 12,
HIT_NORTH = 13,
HIT_SOUTH = 14,
SHOOT_PENALTY = 15,
SHOOT_CATAPULT = 16,
HEAL = 17,
SACRIFICE = 18,
TELEPORT = 19
};
enum class Map {
POINTER = 0,
HOURGLASS = 1,
HERO = 2,
TOWN = 3,
T1_MOVE = 4,
T1_ATTACK = 5,
T1_SAIL = 6,
T1_DISEMBARK = 7,
T1_EXCHANGE = 8,
T1_VISIT = 9,
T2_MOVE = 10,
T2_ATTACK = 11,
T2_SAIL = 12,
T2_DISEMBARK = 13,
T2_EXCHANGE = 14,
T2_VISIT = 15,
T3_MOVE = 16,
T3_ATTACK = 17,
T3_SAIL = 18,
T3_DISEMBARK = 19,
T3_EXCHANGE = 20,
T3_VISIT = 21,
T4_MOVE = 22,
T4_ATTACK = 23,
T4_SAIL = 24,
T4_DISEMBARK = 25,
T4_EXCHANGE = 26,
T4_VISIT = 27,
T1_SAIL_VISIT = 28,
T2_SAIL_VISIT = 29,
T3_SAIL_VISIT = 30,
T4_SAIL_VISIT = 31,
SCROLL_NORTH = 32,
SCROLL_NORTHEAST = 33,
SCROLL_EAST = 34,
SCROLL_SOUTHEAST = 35,
SCROLL_SOUTH = 36,
SCROLL_SOUTHWEST = 37,
SCROLL_WEST = 38,
SCROLL_NORTHWEST = 39,
//POINTER_COPY = 40, // probably unused
TELEPORT = 41,
SCUTTLE_BOAT = 42
};
enum class Spellcast {
SPELL = 0,
};
}
/// handles mouse cursor
@ -45,19 +126,20 @@ class CCursorHandler final
void shiftPos( int &x, int &y );
void updateTexture();
public:
/// Current cursor
Cursor::Type type;
size_t frame;
float frameTime;
void changeGraphic(Cursor::Type type, size_t index);
/// position of cursor
int xpos, ypos;
/// Current cursor
ECursor::ECursorTypes type;
size_t frame;
/// inits cursorHandler - run only once, it's not memleak-proof (rev 1333)
void initCursor();
/// changes cursor graphic for type type (0 - adventure, 1 - combat, 2 - default, 3 - spellbook) and frame index (not used for type 3)
void changeGraphic(ECursor::ECursorTypes type, int index);
public:
CCursorHandler();
~CCursorHandler();
/**
* Replaces the cursor with a custom image.
@ -67,6 +149,27 @@ public:
*/
void dragAndDropCursor (std::unique_ptr<CAnimImage> image);
/// Returns current position of the cursor
Point position() const;
/// Changes cursor to specified index
void set(Cursor::Default index);
void set(Cursor::Map index);
void set(Cursor::Combat index);
void set(Cursor::Spellcast index);
/// Returns current index of cursor
template<typename Index>
Index get()
{
assert((std::is_same<Index, Cursor::Default>::value )|| type != Cursor::Type::DEFAULT );
assert((std::is_same<Index, Cursor::Map>::value )|| type != Cursor::Type::ADVENTURE );
assert((std::is_same<Index, Cursor::Combat>::value )|| type != Cursor::Type::COMBAT );
assert((std::is_same<Index, Cursor::Spellcast>::value )|| type != Cursor::Type::SPELLBOOK );
return static_cast<Index>(frame);
}
void render();
void hide() { showing=false; };
@ -77,6 +180,4 @@ public:
/// Move cursor to screen center
void centerCursor();
CCursorHandler();
~CCursorHandler();
};

View File

@ -121,7 +121,7 @@ void CGuiHandler::pushInt(std::shared_ptr<IShowActivatable> newInt)
if(!listInt.empty())
listInt.front()->deactivate();
listInt.push_front(newInt);
CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
CCS->curh->set(Cursor::Map::POINTER);
newInt->activate();
objsToBlit.push_back(newInt);
totalRedraw();
@ -238,7 +238,7 @@ void CGuiHandler::handleCurrentEvent()
break;
case SDLK_F9:
//not working yet since CClient::run remain locked after CBattleInterface removal
//not working yet since CClient::run remain locked after BattleInterface removal
// if(LOCPLINT->battleInt)
// {
// GH.popInts(1);
@ -451,7 +451,7 @@ void CGuiHandler::renderFrame()
bool acquiredTheLockOnPim = false; //for tracking whether pim mutex locking succeeded
while(!terminate_cond->get() && !(acquiredTheLockOnPim = CPlayerInterface::pim->try_lock())) //try acquiring long until it succeeds or we are told to terminate
boost::this_thread::sleep(boost::posix_time::milliseconds(15));
boost::this_thread::sleep(boost::posix_time::milliseconds(1));
if(acquiredTheLockOnPim)
{
@ -489,7 +489,7 @@ CGuiHandler::CGuiHandler()
statusbar = nullptr;
// Creates the FPS manager and sets the framerate to 48 which is doubled the value of the original Heroes 3 FPS rate
mainFPSmng = new CFramerateManager(48);
mainFPSmng = new CFramerateManager(60);
//do not init CFramerateManager here --AVS
terminate_cond = new CondSh<bool>(false);
@ -623,8 +623,8 @@ void CFramerateManager::framerateDelay()
currentTicks = SDL_GetTicks();
// recalculate timeElapsed for external calls via getElapsed()
// limit it to 1000 ms to avoid breaking animation in case of huge lag (e.g. triggered breakpoint)
timeElapsed = std::min<ui32>(currentTicks - lastticks, 1000);
// limit it to 100 ms to avoid breaking animation in case of huge lag (e.g. triggered breakpoint)
timeElapsed = std::min<ui32>(currentTicks - lastticks, 100);
lastticks = SDL_GetTicks();

View File

@ -267,7 +267,7 @@ void CIntObject::addChild(CIntObject * child, bool adjustPosition)
children.push_back(child);
child->parent_m = this;
if(adjustPosition)
child->pos += pos;
child->pos += pos.topLeft();
if (!active && child->active)
child->deactivate();
@ -289,7 +289,7 @@ void CIntObject::removeChild(CIntObject * child, bool adjustPosition)
children -= child;
child->parent_m = nullptr;
if(adjustPosition)
child->pos -= pos;
child->pos -= pos.topLeft();
}
void CIntObject::redraw()

View File

@ -17,24 +17,42 @@
#include "../Graphics.h"
Canvas::Canvas(SDL_Surface * surface):
surface(surface)
surface(surface),
renderOffset(0,0)
{
surface->refcount++;
}
Canvas::Canvas(Canvas & other):
surface(other.surface)
surface(other.surface),
renderOffset(other.renderOffset)
{
surface->refcount++;
}
Canvas::Canvas(const Point & size)
Canvas::Canvas(Canvas & other, const Rect & newClipRect):
Canvas(other)
{
clipRect.emplace();
SDL_GetClipRect(surface, clipRect.get_ptr());
Rect currClipRect = newClipRect + renderOffset;
SDL_SetClipRect(surface, &currClipRect);
renderOffset += newClipRect.topLeft();
}
Canvas::Canvas(const Point & size):
renderOffset(0,0)
{
surface = CSDL_Ext::newSurface(size.x, size.y);
}
Canvas::~Canvas()
{
if (clipRect)
SDL_SetClipRect(surface, clipRect.get_ptr());
SDL_FreeSurface(surface);
}
@ -42,33 +60,40 @@ void Canvas::draw(std::shared_ptr<IImage> image, const Point & pos)
{
assert(image);
if (image)
image->draw(surface, pos.x, pos.y);
image->draw(surface, renderOffset.x + pos.x, renderOffset.y + pos.y);
}
void Canvas::draw(std::shared_ptr<IImage> image, const Point & pos, const Rect & sourceRect)
{
assert(image);
if (image)
image->draw(surface, pos.x, pos.y, &sourceRect);
image->draw(surface, renderOffset.x + pos.x, renderOffset.y + pos.y, &sourceRect);
}
void Canvas::draw(std::shared_ptr<IImage> image, const Point & pos, const Rect & sourceRect, uint8_t alpha)
{
assert(image);
if (image)
image->draw(surface, renderOffset.x + pos.x, renderOffset.y + pos.y, &sourceRect, alpha);
}
void Canvas::draw(Canvas & image, const Point & pos)
{
blitAt(image.surface, pos.x, pos.y, surface);
blitAt(image.surface, renderOffset.x + pos.x, renderOffset.y + pos.y, surface);
}
void Canvas::drawLine(const Point & from, const Point & dest, const SDL_Color & colorFrom, const SDL_Color & colorDest)
{
CSDL_Ext::drawLine(surface, from.x, from.y, dest.x, dest.y, colorFrom, colorDest);
CSDL_Ext::drawLine(surface, renderOffset.x + from.x, renderOffset.y + from.y, renderOffset.x + dest.x, renderOffset.y + dest.y, colorFrom, colorDest);
}
void Canvas::drawText(const Point & position, const EFonts & font, const SDL_Color & colorDest, ETextAlignment alignment, const std::string & text )
{
switch (alignment)
{
case ETextAlignment::TOPLEFT: return graphics->fonts[font]->renderTextLeft (surface, text, colorDest, position);
case ETextAlignment::CENTER: return graphics->fonts[font]->renderTextCenter(surface, text, colorDest, position);
case ETextAlignment::BOTTOMRIGHT: return graphics->fonts[font]->renderTextRight (surface, text, colorDest, position);
case ETextAlignment::TOPLEFT: return graphics->fonts[font]->renderTextLeft (surface, text, colorDest, renderOffset + position);
case ETextAlignment::CENTER: return graphics->fonts[font]->renderTextCenter(surface, text, colorDest, renderOffset + position);
case ETextAlignment::BOTTOMRIGHT: return graphics->fonts[font]->renderTextRight (surface, text, colorDest, renderOffset + position);
}
}
@ -76,9 +101,9 @@ void Canvas::drawText(const Point & position, const EFonts & font, const SDL_Col
{
switch (alignment)
{
case ETextAlignment::TOPLEFT: return graphics->fonts[font]->renderTextLinesLeft (surface, text, colorDest, position);
case ETextAlignment::CENTER: return graphics->fonts[font]->renderTextLinesCenter(surface, text, colorDest, position);
case ETextAlignment::BOTTOMRIGHT: return graphics->fonts[font]->renderTextLinesRight (surface, text, colorDest, position);
case ETextAlignment::TOPLEFT: return graphics->fonts[font]->renderTextLinesLeft (surface, text, colorDest, renderOffset + position);
case ETextAlignment::CENTER: return graphics->fonts[font]->renderTextLinesCenter(surface, text, colorDest, renderOffset + position);
case ETextAlignment::BOTTOMRIGHT: return graphics->fonts[font]->renderTextLinesRight (surface, text, colorDest, renderOffset + position);
}
}

View File

@ -19,8 +19,15 @@ enum EFonts : int;
/// Class that represents surface for drawing on
class Canvas
{
/// Target surface
SDL_Surface * surface;
/// Clip rect that was in use on surface originally and needs to be restored on destruction
boost::optional<Rect> clipRect;
/// Current rendering area offset, all rendering operations will be moved into selected area
Point renderOffset;
Canvas & operator = (Canvas & other) = delete;
public:
@ -30,6 +37,9 @@ public:
/// copy contructor
Canvas(Canvas & other);
/// creates canvas that only covers specified subsection of a surface
Canvas(Canvas & other, const Rect & clipRect);
/// constructs canvas of specified size
Canvas(const Point & size);
@ -41,6 +51,10 @@ public:
/// renders section of image bounded by sourceRect at specified position
void draw(std::shared_ptr<IImage> image, const Point & pos, const Rect & sourceRect);
/// renders section of image bounded by sourceRect at specified position at specific transparency value
void draw(std::shared_ptr<IImage> image, const Point & pos, const Rect & sourceRect, uint8_t alpha);
/// renders another canvas onto this canvas
void draw(Canvas & image, const Point & pos);

162
client/gui/ColorFilter.cpp Normal file
View File

@ -0,0 +1,162 @@
/*
* Canvas.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 "ColorFilter.h"
#include <SDL2/SDL_pixels.h>
#include "../../lib/JsonNode.h"
SDL_Color ColorFilter::shiftColor(const SDL_Color & in) const
{
int r_out = in.r * r.r + in.g * r.g + in.b * r.b + 255 * r.a;
int g_out = in.r * g.r + in.g * g.g + in.b * g.b + 255 * g.a;
int b_out = in.r * b.r + in.g * b.g + in.b * b.b + 255 * b.a;
int a_out = in.a * a;
vstd::abetween(r_out, 0, 255);
vstd::abetween(g_out, 0, 255);
vstd::abetween(b_out, 0, 255);
vstd::abetween(a_out, 0, 255);
return {
static_cast<uint8_t>(r_out),
static_cast<uint8_t>(g_out),
static_cast<uint8_t>(b_out),
static_cast<uint8_t>(a_out)
};
}
bool ColorFilter::operator != (const ColorFilter & other) const
{
return !(this->operator==(other));
}
bool ColorFilter::operator == (const ColorFilter & other) const
{
return
r.r == other.r.r && r.g && other.r.g && r.b == other.r.b && r.a == other.r.a &&
g.r == other.g.r && g.g && other.g.g && g.b == other.g.b && g.a == other.g.a &&
b.r == other.b.r && b.g && other.b.g && b.b == other.b.b && b.a == other.b.a &&
a == other.a;
}
ColorFilter ColorFilter::genEmptyShifter( )
{
return genAlphaShifter( 1.f);
}
ColorFilter ColorFilter::genAlphaShifter( float alpha )
{
return genMuxerShifter(
{ 1.f, 0.f, 0.f, 0.f },
{ 0.f, 1.f, 0.f, 0.f },
{ 0.f, 0.f, 1.f, 0.f },
alpha);
}
ColorFilter ColorFilter::genRangeShifter( float minR, float minG, float minB, float maxR, float maxG, float maxB )
{
return genMuxerShifter(
{ maxR - minR, 0.f, 0.f, minR },
{ 0.f, maxG - minG, 0.f, minG },
{ 0.f, 0.f, maxB - minB, minB },
1.f);
}
ColorFilter ColorFilter::genMuxerShifter( ChannelMuxer r, ChannelMuxer g, ChannelMuxer b, float a )
{
return ColorFilter(r, g, b, a);
}
ColorFilter ColorFilter::genInterpolated(const ColorFilter & left, const ColorFilter & right, float power)
{
auto lerpMuxer = [=]( const ChannelMuxer & left, const ChannelMuxer & right ) -> ChannelMuxer
{
return {
vstd::lerp(left.r, right.r, power),
vstd::lerp(left.g, right.g, power),
vstd::lerp(left.b, right.b, power),
vstd::lerp(left.a, right.a, power)
};
};
return genMuxerShifter(
lerpMuxer(left.r, right.r),
lerpMuxer(left.g, right.g),
lerpMuxer(left.b, right.b),
vstd::lerp(left.a, right.a, power)
);
}
ColorFilter ColorFilter::genCombined(const ColorFilter & left, const ColorFilter & right)
{
// matrix multiplication
ChannelMuxer r{
left.r.r * right.r.r + left.g.r * right.r.g + left.b.r * right.r.b,
left.r.g * right.r.r + left.g.g * right.r.g + left.b.g * right.r.b,
left.r.b * right.r.r + left.g.b * right.r.g + left.b.b * right.r.b,
left.r.a * right.r.r + left.g.a * right.r.g + left.b.a * right.r.b + 1.f * right.r.a,
};
ChannelMuxer g{
left.r.r * right.g.r + left.g.r * right.g.g + left.b.r * right.g.b,
left.r.g * right.g.r + left.g.g * right.g.g + left.b.g * right.g.b,
left.r.b * right.g.r + left.g.b * right.g.g + left.b.b * right.g.b,
left.r.a * right.g.r + left.g.a * right.g.g + left.b.a * right.g.b + 1.f * right.g.a,
};
ChannelMuxer b{
left.r.r * right.b.r + left.g.r * right.b.g + left.b.r * right.b.b,
left.r.g * right.b.r + left.g.g * right.b.g + left.b.g * right.b.b,
left.r.b * right.b.r + left.g.b * right.b.g + left.b.b * right.b.b,
left.r.a * right.b.r + left.g.a * right.b.g + left.b.a * right.b.b + 1.f * right.b.a,
};
float a = left.a * right.a;
return genMuxerShifter(r,g,b,a);
}
ColorFilter ColorFilter::genFromJson(const JsonNode & entry)
{
ChannelMuxer r{ 1.f, 0.f, 0.f, 0.f };
ChannelMuxer g{ 0.f, 1.f, 0.f, 0.f };
ChannelMuxer b{ 0.f, 0.f, 1.f, 0.f };
float a{ 1.0};
if (!entry["red"].isNull())
{
r.r = entry["red"].Vector()[0].Float();
r.g = entry["red"].Vector()[1].Float();
r.b = entry["red"].Vector()[2].Float();
r.a = entry["red"].Vector()[3].Float();
}
if (!entry["red"].isNull())
{
g.r = entry["green"].Vector()[0].Float();
g.g = entry["green"].Vector()[1].Float();
g.b = entry["green"].Vector()[2].Float();
g.a = entry["green"].Vector()[3].Float();
}
if (!entry["red"].isNull())
{
b.r = entry["blue"].Vector()[0].Float();
b.g = entry["blue"].Vector()[1].Float();
b.b = entry["blue"].Vector()[2].Float();
b.a = entry["blue"].Vector()[3].Float();
}
if (!entry["alpha"].isNull())
a = entry["alpha"].Float();
return genMuxerShifter(r,g,b,a);
}

66
client/gui/ColorFilter.h Normal file
View File

@ -0,0 +1,66 @@
/*
* ColorFilter.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
struct SDL_Color;
VCMI_LIB_NAMESPACE_BEGIN
class JsonNode;
VCMI_LIB_NAMESPACE_END
/// Base class for applying palette transformation on images
class ColorFilter
{
struct ChannelMuxer {
float r, g, b, a;
};
ChannelMuxer r;
ChannelMuxer g;
ChannelMuxer b;
float a;
ColorFilter(ChannelMuxer r, ChannelMuxer g, ChannelMuxer b, float a):
r(r), g(g), b(b), a(a)
{}
public:
SDL_Color shiftColor(const SDL_Color & in) const;
bool operator == (const ColorFilter & other) const;
bool operator != (const ColorFilter & other) const;
/// Generates empty object that has no effect on image
static ColorFilter genEmptyShifter();
/// Generates object that changes alpha (transparency) of the image
static ColorFilter genAlphaShifter( float alpha );
/// Generates object that transforms each channel independently
static ColorFilter genRangeShifter( float minR, float minG, float minB, float maxR, float maxG, float maxB );
/// Generates object that performs arbitrary mixing between any channels
static ColorFilter genMuxerShifter( ChannelMuxer r, ChannelMuxer g, ChannelMuxer b, float a );
/// Combines 2 mixers into a single object
static ColorFilter genCombined(const ColorFilter & left, const ColorFilter & right);
/// Scales down strength of a shifter to a specified factor
static ColorFilter genInterpolated(const ColorFilter & left, const ColorFilter & right, float power);
/// Generates object using supplied Json config
static ColorFilter genFromJson(const JsonNode & entry);
};
struct ColorMuxerEffect
{
std::vector<ColorFilter> filters;
std::vector<float> timePoints;
};

View File

@ -11,6 +11,11 @@
#include "Geometries.h"
#include "../CMT.h"
#include <SDL_events.h>
#include "../../lib/int3.h"
Point::Point(const int3 &a)
:x(a.x),y(a.y)
{}
Point::Point(const SDL_MouseMotionEvent &a)
:x(a.x),y(a.y)
@ -21,7 +26,7 @@ Rect Rect::createCentered( int w, int h )
return Rect(screen->w/2 - w/2, screen->h/2 - h/2, w, h);
}
Rect Rect::around(const Rect &r, int width) /*creates rect around another */
Rect Rect::around(const Rect &r, int width)
{
return Rect(r.x - width, r.y - width, r.w + width * 2, r.h + width * 2);
}
@ -30,3 +35,4 @@ Rect Rect::centerIn(const Rect &r)
{
return Rect(r.x + (r.w - w) / 2, r.y + (r.h - h) / 2, w, h);
}

View File

@ -9,13 +9,16 @@
*/
#pragma once
#include <SDL_video.h>
#include "../../lib/int3.h"
#include <SDL2/SDL_rect.h>
enum class ETextAlignment {TOPLEFT, CENTER, BOTTOMRIGHT};
struct SDL_MouseMotionEvent;
VCMI_LIB_NAMESPACE_BEGIN
class int3;
VCMI_LIB_NAMESPACE_END
// A point with x/y coordinate, used mostly for graphic rendering
struct Point
{
@ -26,13 +29,14 @@ struct Point
{
x = y = 0;
};
Point(int X, int Y)
:x(X),y(Y)
{};
Point(const int3 &a)
:x(a.x),y(a.y)
{}
Point(const SDL_MouseMotionEvent &a);
Point(const int3 &a);
explicit Point(const SDL_MouseMotionEvent &a);
template<typename T>
Point operator+(const T &b) const
@ -73,10 +77,7 @@ struct Point
y -= b.y;
return *this;
}
bool operator<(const Point &b) const //product order
{
return x < b.x && y < b.y;
}
template<typename T> Point& operator=(const T &t)
{
x = t.x;
@ -96,7 +97,7 @@ struct Point
/// Rectangle class, which have a position and a size
struct Rect : public SDL_Rect
{
Rect()//default c-tor
Rect()
{
x = y = w = h = -1;
}
@ -121,60 +122,59 @@ struct Rect : public SDL_Rect
w = r.w;
h = r.h;
}
Rect(const Rect& r) : Rect(static_cast<const SDL_Rect&>(r))
{}
explicit Rect(const SDL_Surface * const &surf)
{
x = y = 0;
w = surf->w;
h = surf->h;
}
Rect(const Rect& r) = default;
Rect centerIn(const Rect &r);
static Rect createCentered(int w, int h);
static Rect around(const Rect &r, int width = 1); //creates rect around another
static Rect around(const Rect &r, int width = 1);
bool isIn(int qx, int qy) const //determines if given point lies inside rect
bool isIn(int qx, int qy) const
{
if (qx > x && qx<x+w && qy>y && qy<y+h)
return true;
return false;
}
bool isIn(const Point & q) const //determines if given point lies inside rect
bool isIn(const Point & q) const
{
return isIn(q.x,q.y);
}
Point topLeft() const //top left corner of this rect
Point topLeft() const
{
return Point(x,y);
}
Point topRight() const //top right corner of this rect
Point topRight() const
{
return Point(x+w,y);
}
Point bottomLeft() const //bottom left corner of this rect
Point bottomLeft() const
{
return Point(x,y+h);
}
Point bottomRight() const //bottom right corner of this rect
Point bottomRight() const
{
return Point(x+w,y+h);
}
Rect operator+(const Rect &p) const //moves this rect by p's rect position
Point center() const
{
return Point(x+w/2,y+h/2);
}
Point dimensions() const
{
return Point(w,h);
}
void moveTo(const Point & dest)
{
x = dest.x;
y = dest.y;
}
Rect operator+(const Point &p) const
{
return Rect(x+p.x,y+p.y,w,h);
}
Rect operator+(const Point &p) const //moves this rect by p's point position
{
return Rect(x+p.x,y+p.y,w,h);
}
Rect& operator=(const Point &p) //assignment operator
{
x = p.x;
y = p.y;
return *this;
}
Rect& operator=(const Rect &p) //assignment operator
Rect& operator=(const Rect &p)
{
x = p.x;
y = p.y;
@ -182,34 +182,21 @@ struct Rect : public SDL_Rect
h = p.h;
return *this;
}
Rect& operator+=(const Rect &p) //works as operator+
Rect& operator+=(const Point &p)
{
x += p.x;
y += p.y;
return *this;
}
Rect& operator+=(const Point &p) //works as operator+
{
x += p.x;
y += p.y;
return *this;
}
Rect& operator-=(const Rect &p) //works as operator+
Rect& operator-=(const Point &p)
{
x -= p.x;
y -= p.y;
return *this;
}
Rect& operator-=(const Point &p) //works as operator+
{
x -= p.x;
y -= p.y;
return *this;
}
template<typename T> Rect operator-(const T &t)
{
return Rect(x - t.x, y - t.y, w, h);
}
Rect operator&(const Rect &p) const //rect intersection
{
bool intersect = true;

View File

@ -13,6 +13,7 @@
#include "InterfaceObjectConfigurable.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../gui/CAnimation.h"
#include "../gui/CGuiHandler.h"
#include "../widgets/CComponent.h"
@ -106,27 +107,9 @@ std::string InterfaceObjectConfigurable::readText(const JsonNode & config) const
if(config.isNull())
return "";
if(config.isNumber())
{
logGlobal->debug("Reading text from generaltext handler id:%d", config.Integer());
return CGI->generaltexth->allTexts[config.Integer()];
}
const std::string delimiter = "/";
std::string s = config.String();
logGlobal->debug("Reading text from translations by key: %s", s);
JsonNode translated = CGI->generaltexth->localizedTexts;
for(size_t p = s.find(delimiter); p != std::string::npos; p = s.find(delimiter))
{
translated = translated[s.substr(0, p)];
s.erase(0, p + delimiter.length());
}
if(s == config.String())
{
logGlobal->warn("Reading non-translated text: %s", s);
return s;
}
return translated[s].String();
return CGI->generaltexth->translate(s);
}
Point InterfaceObjectConfigurable::readPosition(const JsonNode & config) const
@ -211,12 +194,6 @@ std::pair<std::string, std::string> InterfaceObjectConfigurable::readHintText(co
std::pair<std::string, std::string> result;
if(!config.isNull())
{
if(config.isNumber())
{
logGlobal->debug("Reading hint text (zelp) from generaltext handler id:%d", config.Integer());
return CGI->generaltexth->zelp[config.Integer()];
}
if(config.getType() == JsonNode::JsonType::DATA_STRUCT)
{
result.first = readText(config["hover"]);
@ -225,8 +202,9 @@ std::pair<std::string, std::string> InterfaceObjectConfigurable::readHintText(co
}
if(config.getType() == JsonNode::JsonType::DATA_STRING)
{
logGlobal->debug("Reading non-translated hint: %s", config.String());
result.first = result.second = config.String();
logGlobal->debug("Reading hint text (help) from generaltext handler:%sd", config.String());
result.first = CGI->generaltexth->translate( config.String(), "hover");
result.second = CGI->generaltexth->translate( config.String(), "help");
}
}
return result;
@ -257,6 +235,9 @@ std::shared_ptr<CPicture> InterfaceObjectConfigurable::buildPicture(const JsonNo
auto pic = std::make_shared<CPicture>(image, position.x, position.y);
if(!config["visible"].isNull())
pic->visible = config["visible"].Bool();
if ( config["playerColored"].Bool() && LOCPLINT)
pic->colorize(LOCPLINT->playerID);
return pic;
}
@ -299,8 +280,8 @@ std::shared_ptr<CToggleButton> InterfaceObjectConfigurable::buildToggleButton(co
logGlobal->debug("Building widget CToggleButton");
auto position = readPosition(config["position"]);
auto image = config["image"].String();
auto zelp = readHintText(config["zelp"]);
auto button = std::make_shared<CToggleButton>(position, image, zelp);
auto help = readHintText(config["help"]);
auto button = std::make_shared<CToggleButton>(position, image, help);
if(!config["selected"].isNull())
button->setSelected(config["selected"].Bool());
if(!config["imageOrder"].isNull())
@ -319,8 +300,8 @@ std::shared_ptr<CButton> InterfaceObjectConfigurable::buildButton(const JsonNode
logGlobal->debug("Building widget CButton");
auto position = readPosition(config["position"]);
auto image = config["image"].String();
auto zelp = readHintText(config["zelp"]);
auto button = std::make_shared<CButton>(position, image, zelp);
auto help = readHintText(config["help"]);
auto button = std::make_shared<CButton>(position, image, help);
if(!config["items"].isNull())
{
for(const auto & item : config["items"].Vector())

View File

@ -349,12 +349,12 @@ static void drawLineX(SDL_Surface * sur, int x1, int y1, int x2, int y2, const S
for(int x = x1; x <= x2; x++)
{
float f = float(x - x1) / float(x2 - x1);
int y = CSDL_Ext::lerp(y1, y2, f);
int y = vstd::lerp(y1, y2, f);
uint8_t r = CSDL_Ext::lerp(color1.r, color2.r, f);
uint8_t g = CSDL_Ext::lerp(color1.g, color2.g, f);
uint8_t b = CSDL_Ext::lerp(color1.b, color2.b, f);
uint8_t a = CSDL_Ext::lerp(color1.a, color2.a, f);
uint8_t r = vstd::lerp(color1.r, color2.r, f);
uint8_t g = vstd::lerp(color1.g, color2.g, f);
uint8_t b = vstd::lerp(color1.b, color2.b, f);
uint8_t a = vstd::lerp(color1.a, color2.a, f);
Uint8 *p = CSDL_Ext::getPxPtr(sur, x, y);
ColorPutter<4, 0>::PutColor(p, r,g,b,a);
@ -366,12 +366,12 @@ static void drawLineY(SDL_Surface * sur, int x1, int y1, int x2, int y2, const S
for(int y = y1; y <= y2; y++)
{
float f = float(y - y1) / float(y2 - y1);
int x = CSDL_Ext::lerp(x1, x2, f);
int x = vstd::lerp(x1, x2, f);
uint8_t r = CSDL_Ext::lerp(color1.r, color2.r, f);
uint8_t g = CSDL_Ext::lerp(color1.g, color2.g, f);
uint8_t b = CSDL_Ext::lerp(color1.b, color2.b, f);
uint8_t a = CSDL_Ext::lerp(color1.a, color2.a, f);
uint8_t r = vstd::lerp(color1.r, color2.r, f);
uint8_t g = vstd::lerp(color1.g, color2.g, f);
uint8_t b = vstd::lerp(color1.b, color2.b, f);
uint8_t a = vstd::lerp(color1.a, color2.a, f);
Uint8 *p = CSDL_Ext::getPxPtr(sur, x, y);
ColorPutter<4, 0>::PutColor(p, r,g,b,a);
@ -919,7 +919,6 @@ void CSDL_Ext::setDefaultColorKeyPresize(SDL_Surface * surface)
}
template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<2>(int, int);
template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<3>(int, int);
template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<4>(int, int);

View File

@ -49,12 +49,6 @@ inline bool isShiftKeyDown()
}
namespace CSDL_Ext
{
template<typename Int>
Int lerp(Int a, Int b, float f)
{
return a + std::round((b - a) * f);
}
//todo: should this better be assignment operator?
STRONG_INLINE void colorAssign(SDL_Color & dest, const SDL_Color & source)
{
@ -158,57 +152,6 @@ struct ColorPutter
typedef void (*BlitterWithRotationVal)(SDL_Surface *src,SDL_Rect srcRect, SDL_Surface * dst, SDL_Rect dstRect, ui8 rotation);
/// Base class for applying palette transformation on images
class ColorShifter
{
public:
~ColorShifter() = default;
virtual SDL_Color shiftColor(SDL_Color input) const = 0;
};
/// Generic class for palette transformation
/// formula:
/// result = input * factor + added
class ColorShifterMultiplyAndAdd : public ColorShifter
{
SDL_Color added;
SDL_Color factor;
public:
ColorShifterMultiplyAndAdd(SDL_Color factor, SDL_Color added) :
factor(factor),
added(added)
{}
SDL_Color shiftColor(SDL_Color input) const override
{
return {
uint8_t(std::min(255.f, std::round(input.r * float(factor.r) / 255.f + added.r))),
uint8_t(std::min(255.f, std::round(input.g * float(factor.g) / 255.f + added.g))),
uint8_t(std::min(255.f, std::round(input.b * float(factor.b) / 255.f + added.b))),
uint8_t(std::min(255.f, std::round(input.a * float(factor.a) / 255.f + added.a)))
};
}
};
/// Color shifter that allows to specify color to be excempt from changes
class ColorShifterMultiplyAndAddExcept : public ColorShifterMultiplyAndAdd
{
SDL_Color ignored;
public:
ColorShifterMultiplyAndAddExcept(SDL_Color factor, SDL_Color added, SDL_Color ignored) :
ColorShifterMultiplyAndAdd(factor, added),
ignored(ignored)
{}
SDL_Color shiftColor(SDL_Color input) const override
{
if ( input.r == ignored.r && input.g == ignored.g && input.b == ignored.b && input.a == ignored.a)
return input;
return ColorShifterMultiplyAndAdd::shiftColor(input);
}
};
namespace CSDL_Ext
{
/// helper that will safely set and un-set ClipRect for SDL_Surface

View File

@ -92,7 +92,8 @@ CBonusSelection::CBonusSelection()
flagbox = std::make_shared<CFlagBox>(Rect(486, 407, 335, 23));
std::vector<std::string> difficulty;
boost::split(difficulty, CGI->generaltexth->allTexts[492], boost::is_any_of(" "));
std::string difficultyString = CGI->generaltexth->allTexts[492];
boost::split(difficulty, difficultyString, boost::is_any_of(" "));
labelDifficulty = std::make_shared<CLabel>(689, 432, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, difficulty.back());
for(size_t b = 0; b < difficultyIcons.size(); ++b)
@ -141,8 +142,6 @@ void CBonusSelection::loadPositionsOfGraphics()
idx++;
}
assert(idx == CGI->generaltexth->campaignMapNames.size());
}
void CBonusSelection::createBonusesIcons()

View File

@ -136,10 +136,10 @@ void CLobbyScreen::startScenario(bool allowOnlyAI)
logGlobal->error("Exception during startScenario: %s", e.what());
if(std::string(e.what()) == "ExceptionNoHuman")
CInfoWindow::showInfoDialog(std::ref(CGI->generaltexth->allTexts[530]), CInfoWindow::TCompsInfo(), PlayerColor(1));
CInfoWindow::showInfoDialog(CGI->generaltexth->allTexts[530], CInfoWindow::TCompsInfo(), PlayerColor(1));
if(std::string(e.what()) == "ExceptionNoTemplate")
CInfoWindow::showInfoDialog(std::ref(CGI->generaltexth->allTexts[751]), CInfoWindow::TCompsInfo(), PlayerColor(1));
CInfoWindow::showInfoDialog(CGI->generaltexth->allTexts[751], CInfoWindow::TCompsInfo(), PlayerColor(1));
}
catch(...)
{

View File

@ -307,7 +307,7 @@ CChatBox::CChatBox(const Rect & rect)
: CIntObject(KEYBOARD | TEXTINPUT)
{
OBJ_CONSTRUCTION;
pos += rect;
pos += rect.topLeft();
captureAllKeys = true;
type |= REDRAW_PARENT;
@ -341,7 +341,7 @@ void CChatBox::addNewMessage(const std::string & text)
CFlagBox::CFlagBox(const Rect & rect)
: CIntObject(RCLICK)
{
pos += rect;
pos += rect.topLeft();
pos.w = rect.w;
pos.h = rect.h;
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;

View File

@ -410,9 +410,7 @@ TemplatesDropBox::TemplatesDropBox(RandomMapTab & randomMapTab, int3 size):
addCallback("sliderMove", std::bind(&TemplatesDropBox::sliderMove, this, std::placeholders::_1));
OBJ_CONSTRUCTION;
pos = randomMapTab.pos.topLeft();
pos.w = randomMapTab.pos.w;
pos.h = randomMapTab.pos.h;
pos = randomMapTab.pos;
build(config);

View File

@ -99,8 +99,8 @@ bool mapSorter::operator()(const std::shared_ptr<CMapInfo> aaa, const std::share
switch(sortBy)
{
case _numOfMaps: //by number of maps in campaign
return CGI->generaltexth->campaignRegionNames[aaa->campaignHeader->mapVersion].size() <
CGI->generaltexth->campaignRegionNames[bbb->campaignHeader->mapVersion].size();
return CGI->generaltexth->getCampaignLength(aaa->campaignHeader->mapVersion) <
CGI->generaltexth->getCampaignLength(bbb->campaignHeader->mapVersion);
break;
case _name: //by name
return boost::ilexicographical_compare(aaa->campaignHeader->name, bbb->campaignHeader->name);
@ -660,7 +660,7 @@ void SelectionTab::ListItem::updateItem(std::shared_ptr<CMapInfo> info, bool sel
iconLossCondition->disable();
labelNumberOfCampaignMaps->enable();
std::ostringstream ostr(std::ostringstream::out);
ostr << CGI->generaltexth->campaignRegionNames[info->campaignHeader->mapVersion].size();
ostr << CGI->generaltexth->getCampaignLength(info->campaignHeader->mapVersion);
labelNumberOfCampaignMaps->setText(ostr.str());
labelNumberOfCampaignMaps->setColor(color);
}

View File

@ -205,7 +205,7 @@ static std::function<void()> genCommand(CMenuScreen * menu, std::vector<std::str
break;
case 4: //exit
{
return std::bind(CInfoWindow::showYesNoDialog, std::ref(CGI->generaltexth->allTexts[69]), std::vector<std::shared_ptr<CComponent>>(), do_quit, 0, PlayerColor(1));
return std::bind(CInfoWindow::showYesNoDialog, CGI->generaltexth->allTexts[69], std::vector<std::shared_ptr<CComponent>>(), do_quit, 0, PlayerColor(1));
}
break;
case 5: //highscores

View File

@ -237,7 +237,7 @@ void CHeroList::CHeroItem::open()
void CHeroList::CHeroItem::showTooltip()
{
CRClickPopup::createAndPush(hero, GH.current->motion);
CRClickPopup::createAndPush(hero, Point(GH.current->motion));
}
std::string CHeroList::CHeroItem::getHoverText()
@ -329,7 +329,7 @@ void CTownList::CTownItem::open()
void CTownList::CTownItem::showTooltip()
{
CRClickPopup::createAndPush(town, GH.current->motion);
CRClickPopup::createAndPush(town, Point(GH.current->motion));
}
std::string CTownList::CTownItem::getHoverText()
@ -975,11 +975,8 @@ void CInGameConsole::show(SDL_Surface * to)
boost::unique_lock<boost::mutex> lock(texts_mx);
for(auto it = texts.begin(); it != texts.end(); ++it, ++number)
{
Point leftBottomCorner(0, screen->h);
if(LOCPLINT->battleInt)
{
leftBottomCorner = LOCPLINT->battleInt->pos.bottomLeft();
}
Point leftBottomCorner(0, pos.h);
graphics->fonts[FONT_MEDIUM]->renderTextLeft(to, it->first, Colors::GREEN,
Point(leftBottomCorner.x + 50, leftBottomCorner.y - (int)texts.size() * 20 - 80 + number*20));

View File

@ -22,6 +22,7 @@
#include "../gui/CGuiHandler.h"
#include "../windows/InfoWindows.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/CGeneralTextHandler.h"
void CButton::update()
{
@ -285,9 +286,12 @@ std::pair<std::string, std::string> CButton::tooltip()
return std::pair<std::string, std::string>();
}
std::pair<std::string, std::string> CButton::tooltip(const JsonNode & localizedTexts)
std::pair<std::string, std::string> CButton::tooltipLocalized(const std::string & key)
{
return std::make_pair(localizedTexts["label"].String(), localizedTexts["help"].String());
return std::make_pair(
CGI->generaltexth->translate(key, "hover"),
CGI->generaltexth->translate(key, "help")
);
}
std::pair<std::string, std::string> CButton::tooltip(const std::string & hover, const std::string & help)
@ -457,10 +461,10 @@ int CToggleGroup::getSelected() const
return selectedID;
}
CVolumeSlider::CVolumeSlider(const Point & position, const std::string & defName, const int value, const std::pair<std::string, std::string> * const help)
CVolumeSlider::CVolumeSlider(const Point & position, const std::string & defName, const int value, ETooltipMode mode)
: CIntObject(LCLICK | RCLICK | WHEEL),
value(value),
helpHandlers(help)
mode(mode)
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
animImage = std::make_shared<CAnimImage>(std::make_shared<CAnimation>(defName), 0, 0, position.x, position.y),
@ -517,8 +521,10 @@ void CVolumeSlider::clickRight(tribool down, bool previousState)
{
double px = GH.current->motion.x - pos.x;
int index = static_cast<int>(px / static_cast<double>(pos.w) * animImage->size());
std::string hoverText = helpHandlers[index].first;
std::string helpBox = helpHandlers[index].second;
size_t helpIndex = index + (mode == MUSIC ? 326 : 336);
std::string helpBox = CGI->generaltexth->translate("core.help", helpIndex, "help" );
if(!helpBox.empty())
CRClickPopup::createAndPush(helpBox);
if(GH.statusbar)

View File

@ -115,7 +115,7 @@ public:
/// generates tooltip that can be passed into constructor
static std::pair<std::string, std::string> tooltip();
static std::pair<std::string, std::string> tooltip(const JsonNode & localizedTexts);
static std::pair<std::string, std::string> tooltipLocalized(const std::string & key);
static std::pair<std::string, std::string> tooltip(const std::string & hover, const std::string & help = "");
};
@ -191,19 +191,25 @@ public:
/// A typical slider for volume with an animated indicator
class CVolumeSlider : public CIntObject
{
public:
enum ETooltipMode
{
MUSIC,
SOUND
};
private:
int value;
CFunctionList<void(int)> onChange;
std::shared_ptr<CAnimImage> animImage;
const std::pair<std::string, std::string> * const helpHandlers;
ETooltipMode mode;
void setVolume(const int v);
public:
/// @param position coordinates of slider
/// @param defName name of def animation for slider
/// @param value initial value for volume
/// @param help pointer to first helptext of slider
CVolumeSlider(const Point & position, const std::string & defName, const int value,
const std::pair<std::string, std::string> * const help);
/// @param mode that determines tooltip texts
CVolumeSlider(const Point & position, const std::string & defName, const int value, ETooltipMode mode);
void moveTo(int id);
void addCallback(std::function<void(int)> callback);

View File

@ -126,7 +126,7 @@ void CHeroArtPlace::clickLeft(tribool down, bool previousState)
if(ourArt && !down && previousState && !ourOwner->commonInfo->src.AOH)
{
if(ourArt->artType->id == ArtifactID::SPELLBOOK)
GH.pushIntT<CSpellWindow>(ourOwner->curHero, LOCPLINT, LOCPLINT->battleInt);
GH.pushIntT<CSpellWindow>(ourOwner->curHero, LOCPLINT, LOCPLINT->battleInt.get());
}
if (!down && previousState)
@ -1008,7 +1008,7 @@ CCommanderArtPlace::CCommanderArtPlace(Point position, const CGHeroInstance * co
void CCommanderArtPlace::clickLeft(tribool down, bool previousState)
{
if (ourArt && text.size() && down)
LOCPLINT->showYesNoDialog(CGI->generaltexth->localizedTexts["commanderWindow"]["artifactMessage"].String(), [this](){ returnArtToHeroCallback(); }, [](){});
LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.commanderWindow.artifactMessage"), [this](){ returnArtToHeroCallback(); }, [](){});
}
void CCommanderArtPlace::clickRight(tribool down, bool previousState)

View File

@ -443,7 +443,7 @@ CComponentBox::CComponentBox(std::vector<std::shared_ptr<CComponent>> _component
components(_components)
{
type |= REDRAW_PARENT;
pos = position + pos;
pos = position + pos.topLeft();
placeComponents(false);
}
@ -452,7 +452,7 @@ CComponentBox::CComponentBox(std::vector<std::shared_ptr<CSelectableComponent>>
onSelect(_onSelect)
{
type |= REDRAW_PARENT;
pos = position + pos;
pos = position + pos.topLeft();
placeComponents(true);
assert(!components.empty());

View File

@ -167,7 +167,7 @@ void CPicture::scaleTo(Point size)
void CPicture::createSimpleRect(const Rect &r, bool screenFormat, ui32 color)
{
pos += r;
pos += r.topLeft();
pos.w = r.w;
pos.h = r.h;
if(screenFormat)
@ -466,8 +466,8 @@ void CShowableAnim::rotate(bool on, bool vertical)
flags &= ~flag;
}
CCreatureAnim::CCreatureAnim(int x, int y, std::string name, ui8 flags, EAnimType type):
CShowableAnim(x,y,name,flags,4,type)
CCreatureAnim::CCreatureAnim(int x, int y, std::string name, ui8 flags, ECreatureAnimType type):
CShowableAnim(x,y,name,flags,4,size_t(type))
{
xOffset = 0;
yOffset = 0;
@ -475,45 +475,60 @@ CCreatureAnim::CCreatureAnim(int x, int y, std::string name, ui8 flags, EAnimTyp
void CCreatureAnim::loopPreview(bool warMachine)
{
std::vector<EAnimType> available;
std::vector<ECreatureAnimType> available;
static const ECreatureAnimType creaPreviewList[] = {
ECreatureAnimType::HOLDING,
ECreatureAnimType::HITTED,
ECreatureAnimType::DEFENCE,
ECreatureAnimType::ATTACK_FRONT,
ECreatureAnimType::SPECIAL_FRONT
};
static const ECreatureAnimType machPreviewList[] = {
ECreatureAnimType::HOLDING,
ECreatureAnimType::MOVING,
ECreatureAnimType::SHOOT_UP,
ECreatureAnimType::SHOOT_FRONT,
ECreatureAnimType::SHOOT_DOWN
};
static const EAnimType creaPreviewList[] = {HOLDING, HITTED, DEFENCE, ATTACK_FRONT, CAST_FRONT};
static const EAnimType machPreviewList[] = {HOLDING, MOVING, SHOOT_UP, SHOOT_FRONT, SHOOT_DOWN};
auto & previewList = warMachine ? machPreviewList : creaPreviewList;
for (auto & elem : previewList)
if (anim->size(elem))
if (anim->size(size_t(elem)))
available.push_back(elem);
size_t rnd = CRandomGenerator::getDefault().nextInt((int)available.size() * 2 - 1);
if (rnd >= available.size())
{
EAnimType type;
if ( anim->size(MOVING) == 0 )//no moving animation present
type = HOLDING;
ECreatureAnimType type;
if ( anim->size(size_t(ECreatureAnimType::MOVING)) == 0 )//no moving animation present
type = ECreatureAnimType::HOLDING;
else
type = MOVING;
type = ECreatureAnimType::MOVING;
//display this anim for ~1 second (time is random, but it looks good)
for (size_t i=0; i< 12/anim->size(type) + 1; i++)
for (size_t i=0; i< 12/anim->size(size_t(type)) + 1; i++)
addLast(type);
}
else
addLast(available[rnd]);
}
void CCreatureAnim::addLast(EAnimType newType)
void CCreatureAnim::addLast(ECreatureAnimType newType)
{
if (type != MOVING && newType == MOVING)//starting moving - play init sequence
auto currType = ECreatureAnimType(group);
if (currType != ECreatureAnimType::MOVING && newType == ECreatureAnimType::MOVING)//starting moving - play init sequence
{
queue.push( MOVE_START );
queue.push( ECreatureAnimType::MOVE_START );
}
else if (type == MOVING && newType != MOVING )//previous anim was moving - finish it
else if (currType == ECreatureAnimType::MOVING && newType != ECreatureAnimType::MOVING )//previous anim was moving - finish it
{
queue.push( MOVE_END );
queue.push( ECreatureAnimType::MOVE_END );
}
if (newType == TURN_L || newType == TURN_R)
if (newType == ECreatureAnimType::TURN_L || newType == ECreatureAnimType::TURN_R)
queue.push(newType);
queue.push(newType);
@ -522,28 +537,28 @@ void CCreatureAnim::addLast(EAnimType newType)
void CCreatureAnim::reset()
{
//if we are in the middle of rotation - set flag
if (type == TURN_L && !queue.empty() && queue.front() == TURN_L)
if (group == size_t(ECreatureAnimType::TURN_L) && !queue.empty() && queue.front() == ECreatureAnimType::TURN_L)
rotate(true);
if (type == TURN_R && !queue.empty() && queue.front() == TURN_R)
if (group == size_t(ECreatureAnimType::TURN_R) && !queue.empty() && queue.front() == ECreatureAnimType::TURN_R)
rotate(false);
while (!queue.empty())
{
EAnimType at = queue.front();
ECreatureAnimType at = queue.front();
queue.pop();
if (set(at))
if (set(size_t(at)))
return;
}
if (callback)
callback();
while (!queue.empty())
{
EAnimType at = queue.front();
ECreatureAnimType at = queue.front();
queue.pop();
if (set(at))
if (set(size_t(at)))
return;
}
set(HOLDING);
set(size_t(ECreatureAnimType::HOLDING));
}
void CCreatureAnim::startPreview(bool warMachine)
@ -551,9 +566,9 @@ void CCreatureAnim::startPreview(bool warMachine)
callback = std::bind(&CCreatureAnim::loopPreview, this, warMachine);
}
void CCreatureAnim::clearAndSet(EAnimType type)
void CCreatureAnim::clearAndSet(ECreatureAnimType type)
{
while (!queue.empty())
queue.pop();
set(type);
set(size_t(type));
}

View File

@ -11,6 +11,7 @@
#include "../gui/CIntObject.h"
#include "../gui/SDL_Extensions.h"
#include "../battle/BattleConstants.h"
struct SDL_Surface;
struct Rect;
@ -165,56 +166,9 @@ public:
/// Creature-dependend animations like attacking, moving,...
class CCreatureAnim: public CShowableAnim
{
public:
enum EHeroAnimType
{
HERO_HOLDING = 0,
HERO_IDLE = 1, // idling movement that happens from time to time
HERO_DEFEAT = 2, // played when army loses stack or on friendly fire
HERO_VICTORY = 3, // when enemy stack killed or huge damage is dealt
HERO_CAST_SPELL = 4 // spellcasting
};
enum EAnimType // list of creature animations, numbers were taken from def files
{
MOVING=0,
MOUSEON=1,
HOLDING=2,
HITTED=3,
DEFENCE=4,
DEATH=5,
DEATH_RANGED=6,
TURN_L=7,
TURN_R=8, //same
//TURN_L2=9, //identical to previous?
//TURN_R2=10,
ATTACK_UP=11,
ATTACK_FRONT=12,
ATTACK_DOWN=13,
SHOOT_UP=14,
SHOOT_FRONT=15,
SHOOT_DOWN=16,
CAST_UP=17,
CAST_FRONT=18,
CAST_DOWN=19,
MOVE_START=20,
MOVE_END=21,
DEAD = 22, // new group, used to show dead stacks. If empty - last frame from "DEATH" will be copied here
DEAD_RANGED = 23, // new group, used to show dead stacks (if DEATH_RANGED was used). If empty - last frame from "DEATH_RANGED" will be copied here
VCMI_CAST_UP = 30,
VCMI_CAST_FRONT = 31,
VCMI_CAST_DOWN = 32,
VCMI_2HEX_UP = 40,
VCMI_2HEX_FRONT = 41,
VCMI_2HEX_DOWN = 42
};
private:
//queue of animations waiting to be displayed
std::queue<EAnimType> queue;
std::queue<ECreatureAnimType> queue;
//this function is used as callback if preview flag was set during construction
void loopPreview(bool warMachine);
@ -224,13 +178,13 @@ public:
void reset() override;
//add sequence to the end of queue
void addLast(EAnimType newType);
void addLast(ECreatureAnimType newType);
void startPreview(bool warMachine);
//clear queue and set animation to this sequence
void clearAndSet(EAnimType type);
void clearAndSet(ECreatureAnimType type);
CCreatureAnim(int x, int y, std::string name, ui8 flags = 0, EAnimType = HOLDING);
CCreatureAnim(int x, int y, std::string name, ui8 flags = 0, ECreatureAnimType = ECreatureAnimType::HOLDING);
};

View File

@ -68,7 +68,7 @@ LRClickableAreaWText::LRClickableAreaWText()
LRClickableAreaWText::LRClickableAreaWText(const Rect &Pos, const std::string &HoverText, const std::string &ClickText)
{
init();
pos = Pos + pos;
pos = Pos + pos.topLeft();
hoverText = HoverText;
text = ClickText;
}
@ -430,7 +430,7 @@ MoraleLuckBox::MoraleLuckBox(bool Morale, const Rect &r, bool Small)
small(Small)
{
bonusValue = 0;
pos = r + pos;
pos = r + pos.topLeft();
defActions = 255-DISPOSE;
}

View File

@ -478,7 +478,7 @@ CTextInput::CTextInput(const Rect & Pos, EFonts font, const CFunctionList<void(c
CTextInput::CTextInput(const Rect & Pos, const Point & bgOffset, const std::string & bgName, const CFunctionList<void(const std::string &)> & CB)
:cb(CB), CFocusable(std::make_shared<CKeyboardFocusListener>(this))
{
pos += Pos;
pos += Pos.topLeft();
pos.h = Pos.h;
pos.w = Pos.w;
@ -495,7 +495,7 @@ CTextInput::CTextInput(const Rect & Pos, const Point & bgOffset, const std::stri
CTextInput::CTextInput(const Rect & Pos, SDL_Surface * srf)
:CFocusable(std::make_shared<CKeyboardFocusListener>(this))
{
pos += Pos;
pos += Pos.topLeft();
captureAllKeys = true;
OBJ_CONSTRUCTION;
background = std::make_shared<CPicture>(Pos, 0, true);

View File

@ -64,25 +64,25 @@ static void setScrollingCursor(ui8 direction)
if(direction & CAdvMapInt::RIGHT)
{
if(direction & CAdvMapInt::UP)
CCS->curh->changeGraphic(ECursor::ADVENTURE, 33);
CCS->curh->set(Cursor::Map::SCROLL_NORTHEAST);
else if(direction & CAdvMapInt::DOWN)
CCS->curh->changeGraphic(ECursor::ADVENTURE, 35);
CCS->curh->set(Cursor::Map::SCROLL_SOUTHEAST);
else
CCS->curh->changeGraphic(ECursor::ADVENTURE, 34);
CCS->curh->set(Cursor::Map::SCROLL_EAST);
}
else if(direction & CAdvMapInt::LEFT)
{
if(direction & CAdvMapInt::UP)
CCS->curh->changeGraphic(ECursor::ADVENTURE, 39);
CCS->curh->set(Cursor::Map::SCROLL_NORTHWEST);
else if(direction & CAdvMapInt::DOWN)
CCS->curh->changeGraphic(ECursor::ADVENTURE, 37);
CCS->curh->set(Cursor::Map::SCROLL_SOUTHWEST);
else
CCS->curh->changeGraphic(ECursor::ADVENTURE, 38);
CCS->curh->set(Cursor::Map::SCROLL_WEST);
}
else if(direction & CAdvMapInt::UP)
CCS->curh->changeGraphic(ECursor::ADVENTURE, 32);
CCS->curh->set(Cursor::Map::SCROLL_NORTH);
else if(direction & CAdvMapInt::DOWN)
CCS->curh->changeGraphic(ECursor::ADVENTURE, 36);
CCS->curh->set(Cursor::Map::SCROLL_SOUTH);
}
CTerrainRect::CTerrainRect()
@ -227,7 +227,7 @@ void CTerrainRect::handleHover(const SDL_MouseMotionEvent &sEvent)
if(tHovered != pom) //tile outside the map
{
CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
CCS->curh->set(Cursor::Map::POINTER);
return;
}
@ -243,7 +243,7 @@ void CTerrainRect::hover(bool on)
if (!on)
{
adventureInt->statusbar->clear();
CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
CCS->curh->set(Cursor::Map::POINTER);
}
//Hoverable::hover(on);
}
@ -932,7 +932,10 @@ void CAdvMapInt::activate()
GH.statusbar = statusbar;
if(LOCPLINT)
{
LOCPLINT->cingconsole->activate();
LOCPLINT->cingconsole->pos = this->pos;
}
if(!duringAITurn)
{
@ -959,7 +962,7 @@ void CAdvMapInt::deactivate()
{
scrollingDir = 0;
CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
CCS->curh->set(Cursor::Map::POINTER);
activeMapPanel->deactivate();
if (mode == EAdvMapMode::NORMAL)
{
@ -1117,7 +1120,7 @@ void CAdvMapInt::handleMapScrollingUpdate()
}
else if(scrollingState)
{
CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
CCS->curh->set(Cursor::Map::POINTER);
scrollingState = false;
}
}
@ -1130,7 +1133,7 @@ void CAdvMapInt::handleSwipeUpdate()
auto fixedPos = LOCPLINT->repairScreenPos(swipeTargetPosition);
position.x = fixedPos.x;
position.y = fixedPos.y;
CCS->curh->changeGraphic(ECursor::DEFAULT, 0);
CCS->curh->set(Cursor::Map::POINTER);
updateScreen = true;
minimap.redraw();
swipeMovementRequested = false;
@ -1215,7 +1218,7 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key)
if(itr != LOCPLINT->towns.end())
LOCPLINT->showThievesGuildWindow(*itr);
else
LOCPLINT->showInfoDialog(CGI->generaltexth->localizedTexts["adventureMap"]["noTownWithTavern"].String());
LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.noTownWithTavern"));
}
return;
case SDLK_i:
@ -1247,7 +1250,7 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key)
case SDLK_r:
if(isActive() && LOCPLINT->ctrlPressed())
{
LOCPLINT->showYesNoDialog(CGI->generaltexth->localizedTexts["adventureMap"]["confirmRestartGame"].String(),
LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.adventureMap.confirmRestartGame"),
[](){ LOCPLINT->sendCustomEvent(EUserEvent::RESTART_GAME); }, nullptr);
}
return;
@ -1306,7 +1309,7 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key)
if(townWithMarket) //if any town has marketplace, open window
GH.pushIntT<CMarketplaceWindow>(townWithMarket);
else //if not - complain
LOCPLINT->showInfoDialog(CGI->generaltexth->localizedTexts["adventureMap"]["noTownWithMarket"].String());
LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.noTownWithMarket"));
}
else if(isActive()) //no ctrl, advmapint is on the top => switch to town
{
@ -1648,7 +1651,7 @@ void CAdvMapInt::tileHovered(const int3 &mapPos)
return;
if(!LOCPLINT->cb->isVisible(mapPos))
{
CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
CCS->curh->set(Cursor::Map::POINTER);
statusbar->clear();
return;
}
@ -1674,18 +1677,18 @@ void CAdvMapInt::tileHovered(const int3 &mapPos)
{
case SpellID::SCUTTLE_BOAT:
if(objAtTile && objAtTile->ID == Obj::BOAT)
CCS->curh->changeGraphic(ECursor::ADVENTURE, 42);
CCS->curh->set(Cursor::Map::SCUTTLE_BOAT);
else
CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
CCS->curh->set(Cursor::Map::POINTER);
return;
case SpellID::DIMENSION_DOOR:
{
const TerrainTile * t = LOCPLINT->cb->getTile(mapPos, false);
int3 hpos = selection->getSightCenter();
if((!t || t->isClear(LOCPLINT->cb->getTile(hpos))) && isInScreenRange(hpos, mapPos))
CCS->curh->changeGraphic(ECursor::ADVENTURE, 41);
CCS->curh->set(Cursor::Map::TELEPORT);
else
CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
CCS->curh->set(Cursor::Map::POINTER);
return;
}
}
@ -1696,17 +1699,25 @@ void CAdvMapInt::tileHovered(const int3 &mapPos)
if(objAtTile)
{
if(objAtTile->ID == Obj::TOWN && objRelations != PlayerRelations::ENEMIES)
CCS->curh->changeGraphic(ECursor::ADVENTURE, 3);
CCS->curh->set(Cursor::Map::TOWN);
else if(objAtTile->ID == Obj::HERO && objRelations == PlayerRelations::SAME_PLAYER)
CCS->curh->changeGraphic(ECursor::ADVENTURE, 2);
CCS->curh->set(Cursor::Map::HERO);
else
CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
CCS->curh->set(Cursor::Map::POINTER);
}
else
CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
CCS->curh->set(Cursor::Map::POINTER);
}
else if(const CGHeroInstance * h = curHero())
{
std::array<Cursor::Map, 4> cursorMove = { Cursor::Map::T1_MOVE, Cursor::Map::T2_MOVE, Cursor::Map::T3_MOVE, Cursor::Map::T4_MOVE, };
std::array<Cursor::Map, 4> cursorAttack = { Cursor::Map::T1_ATTACK, Cursor::Map::T2_ATTACK, Cursor::Map::T3_ATTACK, Cursor::Map::T4_ATTACK, };
std::array<Cursor::Map, 4> cursorSail = { Cursor::Map::T1_SAIL, Cursor::Map::T2_SAIL, Cursor::Map::T3_SAIL, Cursor::Map::T4_SAIL, };
std::array<Cursor::Map, 4> cursorDisembark = { Cursor::Map::T1_DISEMBARK, Cursor::Map::T2_DISEMBARK, Cursor::Map::T3_DISEMBARK, Cursor::Map::T4_DISEMBARK, };
std::array<Cursor::Map, 4> cursorExchange = { Cursor::Map::T1_EXCHANGE, Cursor::Map::T2_EXCHANGE, Cursor::Map::T3_EXCHANGE, Cursor::Map::T4_EXCHANGE, };
std::array<Cursor::Map, 4> cursorVisit = { Cursor::Map::T1_VISIT, Cursor::Map::T2_VISIT, Cursor::Map::T3_VISIT, Cursor::Map::T4_VISIT, };
std::array<Cursor::Map, 4> cursorSailVisit = { Cursor::Map::T1_SAIL_VISIT, Cursor::Map::T2_SAIL_VISIT, Cursor::Map::T3_SAIL_VISIT, Cursor::Map::T4_SAIL_VISIT, };
const CGPathNode * pnode = LOCPLINT->cb->getPathsInfo(h)->getPathInfo(mapPos);
assert(pnode);
@ -1717,9 +1728,9 @@ void CAdvMapInt::tileHovered(const int3 &mapPos)
case CGPathNode::NORMAL:
case CGPathNode::TELEPORT_NORMAL:
if(pnode->layer == EPathfindingLayer::LAND)
CCS->curh->changeGraphic(ECursor::ADVENTURE, 4 + turns*6);
CCS->curh->set(cursorMove[turns]);
else
CCS->curh->changeGraphic(ECursor::ADVENTURE, 28 + turns);
CCS->curh->set(cursorSailVisit[turns]);
break;
case CGPathNode::VISIT:
@ -1728,48 +1739,48 @@ void CAdvMapInt::tileHovered(const int3 &mapPos)
if(objAtTile && objAtTile->ID == Obj::HERO)
{
if(selection == objAtTile)
CCS->curh->changeGraphic(ECursor::ADVENTURE, 2);
CCS->curh->set(Cursor::Map::HERO);
else
CCS->curh->changeGraphic(ECursor::ADVENTURE, 8 + turns*6);
CCS->curh->set(cursorExchange[turns]);
}
else if(pnode->layer == EPathfindingLayer::LAND)
CCS->curh->changeGraphic(ECursor::ADVENTURE, 9 + turns*6);
CCS->curh->set(cursorVisit[turns]);
else
CCS->curh->changeGraphic(ECursor::ADVENTURE, 28 + turns);
CCS->curh->set(cursorSailVisit[turns]);
break;
case CGPathNode::BATTLE:
case CGPathNode::TELEPORT_BATTLE:
CCS->curh->changeGraphic(ECursor::ADVENTURE, 5 + turns*6);
CCS->curh->set(cursorAttack[turns]);
break;
case CGPathNode::EMBARK:
CCS->curh->changeGraphic(ECursor::ADVENTURE, 6 + turns*6);
CCS->curh->set(cursorSail[turns]);
break;
case CGPathNode::DISEMBARK:
CCS->curh->changeGraphic(ECursor::ADVENTURE, 7 + turns*6);
CCS->curh->set(cursorDisembark[turns]);
break;
default:
if(objAtTile && objRelations != PlayerRelations::ENEMIES)
{
if(objAtTile->ID == Obj::TOWN)
CCS->curh->changeGraphic(ECursor::ADVENTURE, 3);
CCS->curh->set(Cursor::Map::TOWN);
else if(objAtTile->ID == Obj::HERO && objRelations == PlayerRelations::SAME_PLAYER)
CCS->curh->changeGraphic(ECursor::ADVENTURE, 2);
CCS->curh->set(Cursor::Map::HERO);
else
CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
CCS->curh->set(Cursor::Map::POINTER);
}
else
CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
CCS->curh->set(Cursor::Map::POINTER);
break;
}
}
if(ourInaccessibleShipyard(objAtTile))
{
CCS->curh->changeGraphic(ECursor::ADVENTURE, 6);
CCS->curh->set(Cursor::Map::T1_SAIL);
}
}
@ -1802,7 +1813,7 @@ void CAdvMapInt::tileRClicked(const int3 &mapPos)
return;
}
CRClickPopup::createAndPush(obj, GH.current->motion, ETextAlignment::CENTER);
CRClickPopup::createAndPush(obj, Point(GH.current->motion), ETextAlignment::CENTER);
}
void CAdvMapInt::enterCastingMode(const CSpell * sp)
@ -1849,7 +1860,9 @@ const IShipyard * CAdvMapInt::ourInaccessibleShipyard(const CGObjectInstance *ob
{
const IShipyard *ret = IShipyard::castFrom(obj);
if(!ret || obj->tempOwner != player || CCS->curh->type || (CCS->curh->frame != 6 && CCS->curh->frame != 0))
if(!ret ||
obj->tempOwner != player ||
(CCS->curh->get<Cursor::Map>() != Cursor::Map::T1_SAIL && CCS->curh->get<Cursor::Map>() != Cursor::Map::POINTER))
return nullptr;
return ret;

View File

@ -54,7 +54,7 @@ CBuildingRect::CBuildingRect(CCastleBuildings * Par, const CGTownInstance * Town
pos.y += str->pos.y;
if(!str->borderName.empty())
border = BitmapHandler::loadBitmap(str->borderName, true);
border = BitmapHandler::loadBitmap(str->borderName);
else
border = nullptr;
@ -851,7 +851,7 @@ void CCastleBuildings::enterToTheQuickRecruitmentWindow()
if(hasSomeoneToRecruit)
GH.pushIntT<QuickRecruitmentWindow>(town, pos);
else
CInfoWindow::showInfoDialog(CGI->generaltexth->localizedTexts["townHall"]["noCreaturesToRecruit"].String(), {});
CInfoWindow::showInfoDialog(CGI->generaltexth->translate("vcmi.townHall.noCreaturesToRecruit"), {});
}
void CCastleBuildings::enterFountain(const BuildingID & building, BuildingSubID::EBuildingSubID subID, BuildingID::EBuildingID upgrades)
@ -870,8 +870,8 @@ void CCastleBuildings::enterFountain(const BuildingID & building, BuildingSubID:
{
auto buildingName = town->town->getSpecialBuilding(subID)->Name();
hasNotProduced = std::string(CGI->generaltexth->localizedTexts["townHall"]["hasNotProduced"].String());
hasProduced = std::string(CGI->generaltexth->localizedTexts["townHall"]["hasProduced"].String());
hasNotProduced = std::string(CGI->generaltexth->translate("vcmi.townHall.hasNotProduced"));
hasProduced = std::string(CGI->generaltexth->translate("vcmi.townHall.hasProduced"));
boost::algorithm::replace_first(hasNotProduced, "%s", buildingName);
boost::algorithm::replace_first(hasProduced, "%s", buildingName);
}
@ -1468,7 +1468,7 @@ std::string CBuildWindow::getTextForState(int state)
}
case EBuildingState::MISSING_BASE:
{
std::string msg = CGI->generaltexth->localizedTexts["townHall"]["missingBase"].String();
std::string msg = CGI->generaltexth->translate("vcmi.townHall.missingBase");
ret = boost::str(boost::format(msg) % town->town->buildings.at(building->upgrade)->Name());
break;
}

View File

@ -348,8 +348,8 @@ CStackWindow::ButtonsSection::ButtonsSection(CStackWindow * owner, int yOffset)
parent->redraw(); // FIXME: enable/disable don't redraw screen themselves
};
const JsonNode & text = VLC->generaltexth->localizedTexts["creatureWindow"][btnIDs[buttonIndex]];
parent->switchButtons[buttonIndex] = std::make_shared<CButton>(Point(302 + (int)buttonIndex*40, 5), "stackWindow/upgradeButton", CButton::tooltip(text), onSwitch);
std::string tooltipText = "vcmi.creatureWindow." + btnIDs[buttonIndex];
parent->switchButtons[buttonIndex] = std::make_shared<CButton>(Point(302 + (int)buttonIndex*40, 5), "stackWindow/upgradeButton", CButton::tooltipLocalized(tooltipText), onSwitch);
parent->switchButtons[buttonIndex]->addOverlay(std::make_shared<CAnimImage>("stackWindow/switchModeIcons", buttonIndex));
}
parent->switchButtons[parent->activeTab]->disable();
@ -377,10 +377,7 @@ CStackWindow::CommanderMainSection::CommanderMainSection(CStackWindow * owner, i
auto getSkillDescription = [this](int skillIndex) -> std::string
{
if(CGI->generaltexth->znpc00.size() == 0)
return "";
return CGI->generaltexth->znpc00[151 + (12 * skillIndex) + (parent->info->commander->secondarySkills[skillIndex] * 2)];
return CGI->generaltexth->znpc00[152 + (12 * skillIndex) + (parent->info->commander->secondarySkills[skillIndex] * 2)];
};
for(int index = ECommander::ATTACK; index <= ECommander::SPELL_POWER; ++index)
@ -601,13 +598,12 @@ CStackWindow::MainSection::MainSection(CStackWindow * owner, int yOffset, bool s
parent->stackArtifactIcon = std::make_shared<CAnimImage>("ARTIFACT", art->artType->iconIndex, 0, pos.x, pos.y);
parent->stackArtifactHelp = std::make_shared<LRClickableAreaWTextComp>(Rect(pos, Point(44, 44)), CComponent::artifact);
parent->stackArtifactHelp->type = art->artType->id;
const JsonNode & text = VLC->generaltexth->localizedTexts["creatureWindow"]["returnArtifact"];
if(parent->info->owner)
{
parent->stackArtifactButton = std::make_shared<CButton>(
Point(pos.x - 2 , pos.y + 46), "stackWindow/cancelButton",
CButton::tooltip(text), [=]()
CButton::tooltipLocalized("vcmi.creatureWindow.returnArtifact"), [=]()
{
parent->removeStackArtifact(ArtifactPosition::CREATURE_SLOT);
});
@ -869,9 +865,9 @@ std::string CStackWindow::generateStackExpDescription()
if (!vstd::iswithin(tier, 1, 7))
tier = 0;
int number;
std::string expText = CGI->generaltexth->zcrexp[325];
std::string expText = CGI->generaltexth->translate("vcmi.stackExperience.description");
boost::replace_first(expText, "%s", creature->namePl);
boost::replace_first(expText, "%s", CGI->generaltexth->zcrexp[rank]);
boost::replace_first(expText, "%s", CGI->generaltexth->translate("vcmi.stackExperience.rank", rank));
boost::replace_first(expText, "%i", boost::lexical_cast<std::string>(rank));
boost::replace_first(expText, "%i", boost::lexical_cast<std::string>(stack->experience));
number = static_cast<int>(CGI->creh->expRanks[tier][rank] - stack->experience);
@ -906,13 +902,10 @@ void CStackWindow::setSelection(si32 newSkill, std::shared_ptr<CCommanderSkillIc
{
auto getSkillDescription = [this](int skillIndex, bool selected) -> std::string
{
if(CGI->generaltexth->znpc00.size() == 0)
return "";
if(selected)
return CGI->generaltexth->znpc00[151 + (12 * skillIndex) + ((info->commander->secondarySkills[skillIndex] + 1) * 2)]; //upgrade description
return CGI->generaltexth->znpc00[152 + (12 * skillIndex) + ((info->commander->secondarySkills[skillIndex] + 1) * 2)]; //upgrade description
else
return CGI->generaltexth->znpc00[151 + (12 * skillIndex) + (info->commander->secondarySkills[skillIndex] * 2)];
return CGI->generaltexth->znpc00[152 + (12 * skillIndex) + (info->commander->secondarySkills[skillIndex] * 2)];
};
auto getSkillImage = [this](int skillIndex) -> std::string

View File

@ -128,8 +128,7 @@ CHeroWindow::CHeroWindow(const CGHeroInstance * hero)
if(hero->commander)
{
auto texts = CGI->generaltexth->localizedTexts["heroWindow"]["openCommander"];
commanderButton = std::make_shared<CButton>(Point(317, 18), "buttons/commander", CButton::tooltip(texts), [&](){ commanderWindow(); }, SDLK_c);
commanderButton = std::make_shared<CButton>(Point(317, 18), "buttons/commander", CButton::tooltipLocalized("vcmi.heroWindow.openCommander"), [&](){ commanderWindow(); }, SDLK_c);
}
//right list of heroes

View File

@ -594,7 +594,7 @@ void CKingdomInterface::generateMinesList(const std::vector<const CGObjectInstan
for(int i=0; i<7; i++)
{
std::string value = boost::lexical_cast<std::string>(minesCount[i]);
auto data = std::make_shared<InfoBoxCustom>(value, "", "OVMINES", i, CGI->generaltexth->mines[i].first);
auto data = std::make_shared<InfoBoxCustom>(value, "", "OVMINES", i, CGI->generaltexth->translate("core.minename", i));
minesBox[i] = std::make_shared<InfoBox>(Point(20+i*80, 31+footerPos), InfoBox::POS_INSIDE, InfoBox::SIZE_SMALL, data);
minesBox[i]->removeUsedEvents(LCLICK|RCLICK); //fixes #890 - mines boxes ignore clicks
}

View File

@ -126,15 +126,13 @@ CQuestLog::CQuestLog (const std::vector<QuestInfo> & Quests)
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
const JsonNode & texts = CGI->generaltexth->localizedTexts["questLog"];
minimap = std::make_shared<CQuestMinimap>(Rect(12, 12, 169, 169));
// TextBox have it's own 4 pixel padding from top at least for English. To achieve 10px from both left and top only add 6px margin
description = std::make_shared<CTextBox>("", Rect(205, 18, 385, DESCRIPTION_HEIGHT_MAX), CSlider::BROWN, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE);
ok = std::make_shared<CButton>(Point(539, 398), "IOKAY.DEF", CGI->generaltexth->zelp[445], std::bind(&CQuestLog::close, this), SDLK_RETURN);
// Both button and lable are shifted to -2px by x and y to not make them actually look like they're on same line with quests list and ok button
hideCompleteButton = std::make_shared<CToggleButton>(Point(10, 396), "sysopchk.def", CButton::tooltip(texts["hideComplete"]), std::bind(&CQuestLog::toggleComplete, this, _1));
hideCompleteLabel = std::make_shared<CLabel>(46, 398, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, texts["hideComplete"]["label"].String());
hideCompleteButton = std::make_shared<CToggleButton>(Point(10, 396), "sysopchk.def", CButton::tooltipLocalized("vcmi.questLog.hideComplete"), std::bind(&CQuestLog::toggleComplete, this, _1));
hideCompleteLabel = std::make_shared<CLabel>(46, 398, FONT_MEDIUM, ETextAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->translate("vcmi.questLog.hideComplete.hover"));
slider = std::make_shared<CSlider>(Point(166, 195), 191, std::bind(&CQuestLog::sliderMoved, this, _1), QUEST_COUNT, 0, false, CSlider::BROWN);
recreateLabelList();

View File

@ -562,7 +562,7 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
if(!texts.empty())
owner->myInt->showInfoDialog(texts.front());
else
owner->myInt->showInfoDialog(CGI->generaltexth->localizedTexts["adventureMap"]["spellUnknownProblem"].String());
owner->myInt->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.spellUnknownProblem"));
}
}
else //adventure spell
@ -658,7 +658,7 @@ void CSpellWindow::SpellArea::setSpell(const CSpell * spell)
{
boost::format fmt("%s/%s");
fmt % CGI->generaltexth->allTexts[171 + mySpell->level];
fmt % CGI->generaltexth->levels.at(3+(schoolLevel-1));//lines 4-6
fmt % CGI->generaltexth->levels[3+(schoolLevel-1)];//lines 4-6
level->setText(fmt.str());
}
else

View File

@ -449,14 +449,12 @@ CSystemOptionsWindow::CSystemOptionsWindow()
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
title = std::make_shared<CLabel>(242, 32, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[568]);
const JsonNode & texts = CGI->generaltexth->localizedTexts["systemOptions"];
//left window section
leftGroup = std::make_shared<CLabelGroup>(FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW);
leftGroup->add(122, 64, CGI->generaltexth->allTexts[569]);
leftGroup->add(122, 130, CGI->generaltexth->allTexts[570]);
leftGroup->add(122, 196, CGI->generaltexth->allTexts[571]);
leftGroup->add(122, 262, texts["resolutionButton"]["label"].String());
leftGroup->add(122, 262, CGI->generaltexth->translate("vcmi.systemOptions.resolutionButton.hover"));
leftGroup->add(122, 347, CGI->generaltexth->allTexts[394]);
leftGroup->add(122, 412, CGI->generaltexth->allTexts[395]);
@ -466,8 +464,7 @@ CSystemOptionsWindow::CSystemOptionsWindow()
rightGroup->add(282, 89, CGI->generaltexth->allTexts[573]);
rightGroup->add(282, 121, CGI->generaltexth->allTexts[574]);
rightGroup->add(282, 153, CGI->generaltexth->allTexts[577]);
rightGroup->add(282, 185, texts["creatureWindowButton"]["label"].String());
rightGroup->add(282, 217, texts["fullscreenButton"]["label"].String());
rightGroup->add(282, 217, CGI->generaltexth->translate("vcmi.systemOptions.fullscreenButton.hover"));
//setting up buttons
load = std::make_shared<CButton>(Point(246, 298), "SOLOAD.DEF", CGI->generaltexth->zelp[321], [&](){ bloadf(); }, SDLK_l);
@ -520,10 +517,10 @@ CSystemOptionsWindow::CSystemOptionsWindow()
mapScrollSpeed->setSelected((int)settings["adventure"]["scrollSpeed"].Float());
mapScrollSpeed->addCallback(std::bind(&setIntSetting, "adventure", "scrollSpeed", _1));
musicVolume = std::make_shared<CVolumeSlider>(Point(29, 359), "syslb.def", CCS->musich->getVolume(), &CGI->generaltexth->zelp[326]);
musicVolume = std::make_shared<CVolumeSlider>(Point(29, 359), "syslb.def", CCS->musich->getVolume(), CVolumeSlider::MUSIC);
musicVolume->addCallback(std::bind(&setIntSetting, "general", "music", _1));
effectsVolume = std::make_shared<CVolumeSlider>(Point(29, 425), "syslb.def", CCS->soundh->getVolume(), &CGI->generaltexth->zelp[336]);
effectsVolume = std::make_shared<CVolumeSlider>(Point(29, 425), "syslb.def", CCS->soundh->getVolume(), CVolumeSlider::SOUND);
effectsVolume->addCallback(std::bind(&setIntSetting, "general", "sound", _1));
showReminder = std::make_shared<CToggleButton>(Point(246, 87), "sysopchk.def", CGI->generaltexth->zelp[361], [&](bool value)
@ -544,7 +541,7 @@ CSystemOptionsWindow::CSystemOptionsWindow()
});
spellbookAnim->setSelected(settings["video"]["spellbookAnimation"].Bool());
fullscreen = std::make_shared<CToggleButton>(Point(246, 215), "sysopchk.def", CButton::tooltip(texts["fullscreenButton"]), [&](bool value)
fullscreen = std::make_shared<CToggleButton>(Point(246, 215), "sysopchk.def", CButton::tooltipLocalized("vcmi.systemOptions.fullscreenButton"), [&](bool value)
{
setBoolSetting("video", "fullscreen", value);
});
@ -552,7 +549,7 @@ CSystemOptionsWindow::CSystemOptionsWindow()
onFullscreenChanged([&](const JsonNode &newState){ fullscreen->setSelected(newState.Bool());});
gameResButton = std::make_shared<CButton>(Point(28, 275),"buttons/resolution", CButton::tooltip(texts["resolutionButton"]), std::bind(&CSystemOptionsWindow::selectGameRes, this), SDLK_g);
gameResButton = std::make_shared<CButton>(Point(28, 275),"buttons/resolution", CButton::tooltipLocalized("vcmi.systemOptions.resolutionButton"), std::bind(&CSystemOptionsWindow::selectGameRes, this), SDLK_g);
const auto & screenRes = settings["video"]["screenRes"];
gameResLabel = std::make_shared<CLabel>(170, 292, FONT_MEDIUM, ETextAlignment::CENTER, Colors::YELLOW, resolutionToString(screenRes["width"].Integer(), screenRes["height"].Integer()));
@ -561,7 +558,6 @@ CSystemOptionsWindow::CSystemOptionsWindow()
void CSystemOptionsWindow::selectGameRes()
{
std::vector<std::string> items;
const JsonNode & texts = CGI->generaltexth->localizedTexts["systemOptions"]["resolutionMenu"];
#ifndef VCMI_IOS
SDL_Rect displayBounds;
@ -585,7 +581,11 @@ void CSystemOptionsWindow::selectGameRes()
++i;
}
GH.pushIntT<CObjectListWindow>(items, nullptr, texts["label"].String(), texts["help"].String(), std::bind(&CSystemOptionsWindow::setGameRes, this, _1), currentResolutionIndex);
GH.pushIntT<CObjectListWindow>(items, nullptr,
CGI->generaltexth->translate("vcmi.systemOptions.resolutionMenu.hover"),
CGI->generaltexth->translate("vcmi.systemOptions.resolutionMenu.help"),
std::bind(&CSystemOptionsWindow::setGameRes, this, _1),
currentResolutionIndex);
}
void CSystemOptionsWindow::setGameRes(int index)
@ -1217,7 +1217,7 @@ CExchangeWindow::CExchangeWindow(ObjectInstanceID hero1, ObjectInstanceID hero2,
garr->addSplitBtn(std::make_shared<CButton>( Point( 10, qeLayout ? 122 : 132), "TSBTNS.DEF", CButton::tooltip(CGI->generaltexth->tcommands[3]), splitButtonCallback));
garr->addSplitBtn(std::make_shared<CButton>( Point(744, qeLayout ? 122 : 132), "TSBTNS.DEF", CButton::tooltip(CGI->generaltexth->tcommands[3]), splitButtonCallback));
if (qeLayout && (CGI->generaltexth->qeModCommands.size() >= 5))
if(qeLayout)
{
moveAllGarrButtonLeft = std::make_shared<CButton>(Point(325, 118), QUICK_EXCHANGE_MOD_PREFIX + "/armRight.DEF", CButton::tooltip(CGI->generaltexth->qeModCommands[1]), controller.onMoveArmyToRight());
echangeGarrButton = std::make_shared<CButton>(Point(377, 118), QUICK_EXCHANGE_MOD_PREFIX + "/swapAll.DEF", CButton::tooltip(CGI->generaltexth->qeModCommands[2]), controller.onSwapArmy());

View File

@ -139,6 +139,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
${MAIN_LIB_DIR}/spells/effects/Catapult.cpp
${MAIN_LIB_DIR}/spells/effects/Clone.cpp
${MAIN_LIB_DIR}/spells/effects/Damage.cpp
${MAIN_LIB_DIR}/spells/effects/DemonSummon.cpp
${MAIN_LIB_DIR}/spells/effects/Dispel.cpp
${MAIN_LIB_DIR}/spells/effects/Effect.cpp
${MAIN_LIB_DIR}/spells/effects/Effects.cpp
@ -374,6 +375,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
${MAIN_LIB_DIR}/spells/effects/Catapult.h
${MAIN_LIB_DIR}/spells/effects/Clone.h
${MAIN_LIB_DIR}/spells/effects/Damage.h
${MAIN_LIB_DIR}/spells/effects/DemonSummon.h
${MAIN_LIB_DIR}/spells/effects/Dispel.h
${MAIN_LIB_DIR}/spells/effects/Effect.h
${MAIN_LIB_DIR}/spells/effects/Effects.h

View File

@ -2240,12 +2240,6 @@
"val" : 0,
"valueType" : "BASE_NUMBER"
},
{
"subtype" : "creature.vampireLord",
"type" : "DAEMON_SUMMONING",
"val" : 10,
"valueType" : "BASE_NUMBER"
},
{
"addInfo" : 2,
"subtype" : "spell.lightningBolt",

113
config/battleEffects.json Normal file
View File

@ -0,0 +1,113 @@
{
"colorMuxers" : {
"example" : [
{
// Required. Time point at which this effect will be applied at full puwer.
// Must be greater than time point of previous effect
// During playback, effects will be played with smooth transition
// Effect will end once game reaches time point of the final filter
// Effect of final step will be applied to stack permanently for duration of the spell
// Note that actual speed of animation is subject to changes from in-game animation speed setting
"time" : 0.0,
// Optional. Transformation filter for red, green and blue components of a color
// Applies transformation with specified parameters to each channel. Formula:
// result = red * (value 1) + green * (value 2) + blue * (value 3) + (value 4)
"red" : [ 1.0, 0.0, 0.0, 0.0 ],
"green" : [ 0.0, 1.0, 0.0, 0.0 ],
"blue" : [ 0.0, 0.0, 1.0, 0.0 ],
/// Optional. Transparency filter, makes stack appear semi-transparent, used mostly for fade-in effects
/// Value 0 = full transparency, 1 = fully opaque
"alpha" : 1.0
}
],
"petrification" : [
{
"time" : 0.0
},
{
"time" : 1.0,
// Conversion to grayscale, using human eye perception factor for channels
"red" : [ 0.299, 0.587, 0.114, 0.0 ],
"green" : [ 0.299, 0.587, 0.114, 0.0 ],
"blue" : [ 0.299, 0.587, 0.114, 0.0 ],
}
],
"cloning" : [
{
// No fade in - will be handled by summonFadeIn effect
"time" : 0.0,
"red" : [ 0.5, 0.0, 0.0, 0.0 ],
"green" : [ 0.0, 0.5, 0.0, 0.0 ],
"blue" : [ 0.0, 0.0, 0.5, 0.5 ],
}
],
"summonFadeIn" : [
{
"time" : 0.0,
"alpha" : 0.0
},
{
"time" : 1.0
},
],
"summonFadeOut" : [
{
"time" : 0.0
},
{
"time" : 1.0,
"alpha" : 0.0
},
],
"teleportFadeIn" : [
{
"time" : 0.0,
"alpha" : 0.0
},
{
"time" : 0.2
},
],
"teleportFadeOut" : [
{
"time" : 0.0
},
{
"time" : 0.2,
"alpha" : 0.0
},
],
"bloodlust" : [
{
"time" : 0.0
},
{
"time" : 0.2,
"red" : [ 0.5, 0.0, 0.5, 0.4 ],
"green" : [ 0.0, 1.0, 0.0, 0.0 ],
"blue" : [ 0.0, 0.0, 1.0, 0.0 ],
"alpha" : 1.0
},
{
"time" : 0.4,
"red" : [ 0.6, 0.6, 0.6, 0.0 ],
"green" : [ 0.0, 0.5, 0.0, 0.0 ],
"blue" : [ 0.0, 0.0, 0.5, 0.0 ],
"alpha" : 1.0
},
{
"time" : 0.6,
"red" : [ 0.5, 0.0, 0.5, 0.4 ],
"green" : [ 0.0, 1.0, 0.0, 0.0 ],
"blue" : [ 0.0, 0.0, 1.0, 0.0 ],
"alpha" : 1.0
},
{
"time" : 0.8,
},
],
}
}

View File

@ -84,14 +84,6 @@
}
},
"DAEMON_SUMMONING":
{
"graphics":
{
"icon": "zvs/Lib1.res/RiseDemons"
}
},
"DARKNESS":
{
},

View File

@ -72,12 +72,6 @@
"description": "Immune to Champion charge"
},
"DAEMON_SUMMONING":
{
"name": "Summoner (${subtype.creature})",
"description": "Can rise creatures from corpses"
},
"DARKNESS":
{
"name": "Darkness cover",

View File

@ -216,12 +216,18 @@
"faction": "inferno",
"abilities":
{
"demonSummon" :
"summons50HP" :
{
"type" : "DAEMON_SUMMONING",
"subtype" : "creature.demon",
"type" : "SPECIFIC_SPELL_POWER",
"subtype" : "spell.summonDemons",
"val" : 50
},
"resurrects" :
{
"type" : "SPELLCASTER",
"subtype" : "spell.summonDemons",
"val" : 0
},
"castsAmount" :
{
"type" : "CASTS",

View File

@ -274,7 +274,8 @@
{
"type" : "SPELL_AFTER_ATTACK",
"subtype" : "spell.blind",
"val" : 20
"val" : 20,
"addInfo" : [3,0]
}
},
"upgrades": ["warUnicorn"],
@ -307,7 +308,8 @@
{
"type" : "SPELL_AFTER_ATTACK",
"subtype" : "spell.blind",
"val" : 20
"val" : 20,
"addInfo" : [3,0]
}
},
"graphics" :

View File

@ -27,10 +27,10 @@
"WINNING_HERO_WITH_NO_TROOPS_RETREATS": true,
"BLACK_MARKET_MONTHLY_ARTIFACTS_CHANGE": true,
"NO_RANDOM_SPECIAL_WEEKS_AND_MONTHS": false,
"ATTACK_POINT_DMG_MULTIPLIER": 0.05,
"ATTACK_POINTS_DMG_MULTIPLIER_CAP": 4.0,
"DEFENSE_POINT_DMG_MULTIPLIER": 0.025,
"DEFENSE_POINTS_DMG_MULTIPLIER_CAP": 0.7
"ATTACK_POINT_DMG_MULTIPLIER": 0.05, //every 1 attack point damage influence in battle when attack points > defense points during creature attack
"ATTACK_POINTS_DMG_MULTIPLIER_CAP": 4.0, //limit of damage increase that can be achieved by overpowering attack points
"DEFENSE_POINT_DMG_MULTIPLIER": 0.025, //every 1 defense point damage influence in battle when defense points > attack points during creature attack
"DEFENSE_POINTS_DMG_MULTIPLIER_CAP": 0.7 //limit of damage reduction that can be achieved by overpowering defense points
},
"modules":
{

View File

@ -17,7 +17,7 @@
"type" : "object",
"default": {},
"additionalProperties" : false,
"required" : [ "playerName", "showfps", "music", "sound", "encoding", "swipe", "saveRandomMaps", "saveFrequency", "notifications", "extraDump" ],
"required" : [ "playerName", "showfps", "music", "sound", "encoding", "language", "swipe", "saveRandomMaps", "saveFrequency", "notifications", "extraDump" ],
"properties" : {
"playerName" : {
"type":"string",
@ -51,6 +51,10 @@
"type":"string",
"default" : "Maps/Arrogance"
},
"language" : {
"type":"string",
"default" : "english"
},
"lastSave" : {
"type":"string",
"default" : "NEWGAME"
@ -356,7 +360,7 @@
"properties" : {
"format" : {
"type" : "string",
"default" : "%l %n [%t] - %m"
"default" : "[%c] %l %n - %m"
}
}
},

View File

@ -3,7 +3,9 @@
"index" : 70,
"targetType": "NO_TARGET",
"animation":{
//need special animation
"affect":[ {
"effectName" : "petrification"
} ]
},
"sounds": {
"cast": "PARALYZE"
@ -428,5 +430,52 @@
"bonus.DIRECT_DAMAGE_IMMUNITY" : "normal"
}
}
}
},
"summonDemons" : {
"type": "ability",
"targetType" : "CREATURE",
"name": "Summon Demons",
"school" : {},
"level": 2,
"power": 50,
"defaultGainChance": 0,
"gainChance": {},
"animation":{
},
"sounds": {
"cast": "RESURECT"
},
"levels" : {
"base": {
"description" : "",
"aiValue" : 0,
"power" : 40,
"cost" : 1,
"range" : "0",
"battleEffects":{
"demonSummon":{
"id":"demon",
"permanent":true,
"type":"core:demonSummon"
}
}
},
"none" :{},
"basic" :{},
"advanced" :{},
"expert" :{}
},
"flags" : {
"rising": true,
"positive": true
},
"targetCondition" : {
"noneOf" : {
"bonus.NON_LIVING" : "absolute",
"bonus.SIEGE_WEAPON" : "absolute",
"bonus.UNDEAD" : "absolute",
"bonus.GARGOYLE" : "absolute"
}
}
},
}

View File

@ -21,11 +21,13 @@
"attacker" :{
"animation" : "C17SPE1",
"appearAnimation" : "C17SPE0",
"appearSound" : "QUIKSAND",
"offsetY" : -42
},
"defender" :{
"animation" : "C17SPE1",
"appearAnimation" : "C17SPE0",
"appearSound" : "QUIKSAND",
"offsetY" : -42
}
}
@ -72,11 +74,17 @@
"turnsRemaining" : -1,
"attacker" :{
"animation" : "C09SPF1",
"appearAnimation" : "C09SPF0"
"appearAnimation" : "C09SPF0",
"appearSound" : "LANDMINE",
"triggerAnimation" : "C09SPF3",
"triggerSound" : "LANDKILL"
},
"defender" :{
"animation" : "C09SPF1",
"appearAnimation" : "C09SPF0"
"appearAnimation" : "C09SPF0",
"appearSound" : "LANDMINE",
"triggerAnimation" : "C09SPF3",
"triggerSound" : "LANDKILL"
}
},
"damage":{
@ -588,7 +596,6 @@
"index" : 65,
"targetType" : "CREATURE",
"animation":{
"cast":[2]
},
"sounds": {
"cast": "CLONE"
@ -632,7 +639,6 @@
"index" : 66,
"targetType" : "NO_TARGET",
"animation":{
"cast":[2]
},
"sounds": {
"cast": "SUMNELM"
@ -658,7 +664,6 @@
"index" : 67,
"targetType" : "NO_TARGET",
"animation":{
"cast":[2]
},
"sounds": {
"cast": "SUMNELM"
@ -684,7 +689,6 @@
"index" : 68,
"targetType" : "NO_TARGET",
"animation":{
"cast":[2]
},
"sounds": {
"cast": "SUMNELM"
@ -710,7 +714,6 @@
"index" : 69,
"targetType" : "NO_TARGET",
"animation":{
"cast":[2]
},
"sounds": {
"cast": "SUMNELM"

View File

@ -69,11 +69,8 @@
"affect":["C05SPF0"]
},
"sounds": {
"cast": "FIRESHIE"
"cast": "FIRESHLD"
},
// It looks that fireshield has two separate sounds
// "soundfile":"FIRESHLD.wav"
//
"levels" : {
"base":{
"range" : "0",
@ -440,7 +437,9 @@
"targetType" : "CREATURE",
"animation":{
"affect":["SP12_"] //???
"affect":[ {
"effectName" : "bloodlust"
} ]
},
"sounds": {
"cast": "BLOODLUS"
@ -541,7 +540,7 @@
"targetType" : "CREATURE",
"animation":{
"affect":["C0ACID"]
"affect":["C17SPW0"]
},
"sounds": {
"cast": "WEAKNESS"
@ -1200,7 +1199,7 @@
"effects" : {
"attacksNearestCreature" : {
"type" : "ATTACKS_NEAREST_CREATURE",
"duration" : "STACK_GETS_TURN"
"duration" : "UNTIL_ATTACK"
}
}
},

View File

@ -1,127 +1,84 @@
// This file contains all translateable strings added in VCMI
{
"adventureMap":
{
"monsterThreat" :
{
"title" : "\n\n Threat: ",
"levels" :
[
"Effortless",
"Very Weak",
"Weak",
"A bit weaker",
"Equal",
"A bit stronger",
"Strong",
"Very Strong",
"Challenging",
"Overpowering",
"Deadly",
"Impossible"
]
},
"confirmRestartGame" : "Are you sure you want to restart game?",
"noTownWithMarket": "No available marketplace!",
"noTownWithTavern": "No available town with tavern!",
"spellUnknownProblem": "Unknown problem with this spell, no more information available.",
"playerAttacked" : "Player has been attacked: %s"
},
"server" :
{
"errors" :
{
"existingProcess" : "Another vcmiserver process is running, please terminate it first",
"modsIncompatibility" : "Required mods to load game:"
},
"confirmReconnect" : "Connect to the last session?"
},
"systemOptions" :
{
"fullscreenButton" :
{
"label" : "Fullscreen",
"help" : "{Fullscreen}\n\n If selected, VCMI will run in fullscreen mode, othervice VCMI will run in window"
},
"resolutionButton" :
{
"label" : "Resolution",
"help" : "{Select resolution}\n\n Change in-game screen resolution. Game restart required to apply new resolution."
},
"resolutionMenu" :
{
"label" : "Select resolution",
"help" : "Change in-game screen resolution."
}
},
"townHall" :
{
"missingBase" : "Base building %s must be built first",
"noCreaturesToRecruit" : "There are no creatures to recruit!",
"greetingManaVortex" : "As you near the %s your body is filled with new energy. You have doubled your normal spell points.",
"greetingKnowledge" : "You study the glyphs on the %s and gain insight into the workings of various magics (+1 Knowledge).",
"greetingSpellPower" : "The %s teaches you new ways to focus your magical powers (+1 Power).",
"greetingExperience" : "A visit to the %s teaches you many new skills (+1000 Experience).",
"greetingAttack" : "Some time spent at the %s allows you to learn more effective combat skills (+1 Attack Skill).",
"greetingDefence" : "Spending time in the %s, the experienced warriors therein teach you additional defensive skills (+1 Defense).",
"hasNotProduced" : "The %s has not produced anything yet.",
"hasProduced" : "The %s produced %d %s this week.",
"greetingCustomBonus" : "%s gives you +%d %s%s",
"greetingCustomUntil" : " until next battle.",
"greetingInTownMagicWell" : "%s has restored your spell points to maximum."
},
"logicalExpressions" :
{
"anyOf" : "Any of the following:",
"allOf" : "All of the following:",
"noneOf" : "None of the following:"
},
"heroWindow" :
{
"openCommander" :
{
"label" : "Open commander window",
"help" : "Displays information about commander of this hero"
}
},
"commanderWindow":
{
"artifactMessage": "Do you want to give this artifact back to hero?"
},
"creatureWindow" :
{
"showBonuses" :
{
"label" : "Switch to bonuses view",
"help" : "Displays all active bonuses of the commander"
},
"showSkills" :
{
"label" : "Switch to skills view",
"help" : "Displays all learned skills of the commander"
},
"returnArtifact" :
{
"label" : "Give back artifact",
"help" : "Use this button to return stack artifact back into hero backpack"
}
},
"questLog" :
{
"hideComplete" :
{
"label" : "Hide complete quests",
"help" : "Hide all quests that already completed"
}
},
"randomMapTab":
{
"widgets":
{
"defaultTemplate": "default",
"templateLabel": "Template",
"teamAlignmentsButton": "Setup...",
"teamAlignmentsLabel": "Team alignments"
}
}
"vcmi.adventureMap.monsterThreat.title" : "\n\n Threat: ",
"vcmi.adventureMap.monsterThreat.levels.0" : "Effortless",
"vcmi.adventureMap.monsterThreat.levels.1" : "Very Weak",
"vcmi.adventureMap.monsterThreat.levels.2" : "Weak",
"vcmi.adventureMap.monsterThreat.levels.3" : "A bit weaker",
"vcmi.adventureMap.monsterThreat.levels.4" : "Equal",
"vcmi.adventureMap.monsterThreat.levels.5" : "A bit stronger",
"vcmi.adventureMap.monsterThreat.levels.6" : "Strong",
"vcmi.adventureMap.monsterThreat.levels.7" : "Very Strong",
"vcmi.adventureMap.monsterThreat.levels.8" : "Challenging",
"vcmi.adventureMap.monsterThreat.levels.9" : "Overpowering",
"vcmi.adventureMap.monsterThreat.levels.10" : "Deadly",
"vcmi.adventureMap.monsterThreat.levels.11" : "Impossible",
"vcmi.adventureMap.confirmRestartGame" : "Are you sure you want to restart game?",
"vcmi.adventureMap.noTownWithMarket" : "No available marketplace!",
"vcmi.adventureMap.noTownWithTavern" : "No available town with tavern!",
"vcmi.adventureMap.spellUnknownProblem" : "Unknown problem with this spell, no more information available.",
"vcmi.adventureMap.playerAttacked" : "Player has been attacked: %s",
"vcmi.server.errors.existingProcess" : "Another vcmiserver process is running, please terminate it first",
"vcmi.server.errors.modsIncompatibility" : "Required mods to load game:",
"vcmi.server.confirmReconnect" : "Connect to the last session?",
"vcmi.systemOptions.fullscreenButton.hover" : "Fullscreen",
"vcmi.systemOptions.fullscreenButton.help" : "{Fullscreen}\n\n If selected, VCMI will run in fullscreen mode, othervice VCMI will run in window",
"vcmi.systemOptions.resolutionButton.hover" : "Resolution",
"vcmi.systemOptions.resolutionButton.help" : "{Select resolution}\n\n Change in-game screen resolution. Game restart required to apply new resolution.",
"vcmi.systemOptions.resolutionMenu.hover" : "Select resolution",
"vcmi.systemOptions.resolutionMenu.help" : "Change in-game screen resolution.",
"vcmi.townHall.missingBase" : "Base building %s must be built first",
"vcmi.townHall.noCreaturesToRecruit" : "There are no creatures to recruit!",
"vcmi.townHall.greetingManaVortex" : "As you near the %s your body is filled with new energy. You have doubled your normal spell points.",
"vcmi.townHall.greetingKnowledge" : "You study the glyphs on the %s and gain insight into the workings of various magics (+1 Knowledge).",
"vcmi.townHall.greetingSpellPower" : "The %s teaches you new ways to focus your magical powers (+1 Power).",
"vcmi.townHall.greetingExperience" : "A visit to the %s teaches you many new skills (+1000 Experience).",
"vcmi.townHall.greetingAttack" : "Some time spent at the %s allows you to learn more effective combat skills (+1 Attack Skill).",
"vcmi.townHall.greetingDefence" : "Spending time in the %s, the experienced warriors therein teach you additional defensive skills (+1 Defense).",
"vcmi.townHall.hasNotProduced" : "The %s has not produced anything yet.",
"vcmi.townHall.hasProduced" : "The %s produced %d %s this week.",
"vcmi.townHall.greetingCustomBonus" : "%s gives you +%d %s%s",
"vcmi.townHall.greetingCustomUntil" : " until next battle.",
"vcmi.townHall.greetingInTownMagicWell" : "%s has restored your spell points to maximum.",
"vcmi.logicalExpressions.anyOf" : "Any of the following:",
"vcmi.logicalExpressions.allOf" : "All of the following:",
"vcmi.logicalExpressions.noneOf" : "None of the following:",
"vcmi.heroWindow.openCommander.hover" : "Open commander window",
"vcmi.heroWindow.openCommander.help" : "Displays information about commander of this hero",
"vcmi.commanderWindow.artifactMessage" : "Do you want to give this artifact back to hero?",
"vcmi.creatureWindow.showBonuses.hover" : "Switch to bonuses view",
"vcmi.creatureWindow.showBonuses.help" : "Displays all active bonuses of the commander",
"vcmi.creatureWindow.showSkills.hover" : "Switch to skills view",
"vcmi.creatureWindow.showSkills.help" : "Displays all learned skills of the commander",
"vcmi.creatureWindow.returnArtifact.hover" : "Give back artifact",
"vcmi.creatureWindow.returnArtifact.help" : "Use this button to return stack artifact back into hero backpack",
"vcmi.questLog.hideComplete.hover" : "Hide complete quests",
"vcmi.questLog.hideComplete.help" : "Hide all quests that already completed",
"vcmi.randomMapTab.widgets.defaultTemplate" : "default",
"vcmi.randomMapTab.widgets.templateLabel" : "Template",
"vcmi.randomMapTab.widgets.teamAlignmentsButton" : "Setup...",
"vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "Team alignments",
// few strings from WoG used by vcmi
"vcmi.stackExperience.description" : "» S t a c k E x p e r i e n c e D e t a i l s «\n\nCreature Type ................... : %s\nExperience Rank ................. : %s (%i)\nExperience Points ............... : %i\nExperience Points to Next Rank .. : %i\nMaximum Experience per Battle ... : %i%% (%i)\nNumber of Creatures in stack .... : %i\nMaximum New Recruits\n without losing current Rank .... : %i\nExperience Multiplier ........... : %.2f\nUpgrade Multiplier .............. : %.2f\nExperience after Rank 10 ........ : %i\nMaximum New Recruits to remain at\n Rank 10 if at Maximum Experience : %i",
"vcmi.stackExperience.rank.1" : "Basic",
"vcmi.stackExperience.rank.2" : "Novice",
"vcmi.stackExperience.rank.3" : "Trained",
"vcmi.stackExperience.rank.4" : "Skilled",
"vcmi.stackExperience.rank.5" : "Proven",
"vcmi.stackExperience.rank.6" : "Veteran",
"vcmi.stackExperience.rank.7" : "Adept",
"vcmi.stackExperience.rank.8" : "Expert",
"vcmi.stackExperience.rank.9" : "Elite",
"vcmi.stackExperience.rank.10" : "Master",
"vcmi.stackExperience.rank.11" : "Ace"
}

View File

@ -1,144 +0,0 @@
{
"items":
[
{
"type": "button",
"name": "options",
"position": {"x": 3, "y": 5},
"image": "icm003",
"zelp": 381,
"callback": "options",
"hotkey": "o"
},
{
"type": "button",
"name": "surrender",
"position": {"x": 54, "y": 5},
"image": "icm001",
"zelp": 379,
"callback": "surrender",
"hotkey": "s"
},
{
"type": "button",
"name": "flee",
"position": {"x": 105, "y": 5},
"image": "icm002",
"zelp": 380,
"callback": "flee",
"hotkey": "r"
},
{
"type": "button",
"name": "autofight",
"position": {"x": 157, "y": 5},
"image": "icm004",
"zelp": 382,
"callback": "autofight",
"hotkey": "a"
},
{
"type": "button",
"name": "cast",
"position": {"x": 645, "y": 5},
"image": "icm005",
"zelp": 385,
"callback": "spellbook",
"hotkey": "c"
},
{
"type": "button",
"name": "wait",
"position": {"x": 696, "y": 5},
"image": "icm006",
"zelp": 386,
"callback": "wait",
"hotkey": "w"
},
{
"type": "button",
"name": "defence",
"position": {"x": 747, "y": 5},
"image": "icm007",
"zelp": 387,
"callback": "defence",
"hotkey": ["d", "space"]
},
{
"type": "button",
"name": "consoleUp",
"position": {"x": 624, "y": 5},
"image": "ComSlide",
"zelp": "",
"callback": "consoleUp",
"imageOrder": [0, 1, 0, 0],
"hotkey": "up"
},
{
"type": "button",
"name": "consoleDown",
"position": {"x": 624, "y": 24},
"image": "ComSlide",
"zelp": "",
"callback": "consoleDown",
"imageOrder": [2, 3, 2, 2],
"hotkey": "down"
},
{
"type": "battleConsole",
"name": "console",
"rect": {"x": 211, "y": 4, "w": 406, "h": 38}
}
],
"variables":
{
"tacticItems":
[
{
"type": "button",
"name": "tacticNext",
"position": {"x": 213, "y": 4},
"image": "icm011",
"zelp": "",
"callback": "tacticNext",
"hotkey": "space"
},
{
"type": "button",
"name": "tacticEnd",
"position": {"x": 419, "y": 4},
"image": "icm012",
"zelp": "",
"callback": "tacticEnd",
"hotkey": "enter"
},
{
"type": "picture",
"name": "menu",
"position": {"x": 0, "y": 0},
"image": "COPLACBR.bmp"
}
],
"battleItems":
[
{
"type": "picture",
"name": "menu",
"position": {"x": 0, "y": 0},
"image": "CBAR.bmp"
}
]
}
}

View File

@ -0,0 +1,133 @@
{
"items":
[
{
"type": "picture",
"name": "menuTactics",
"playerColored" : true,
"position": {"x": 0, "y": 556},
"image": "COPLACBR.bmp"
},
{
"type": "picture",
"name": "menuBattle",
"playerColored" : true,
"position": {"x": 1, "y": 555},
"image": "CBAR.bmp"
},
{
"type": "button",
"name": "options",
"position": {"x": 4, "y": 560},
"image": "icm003",
"help": "core.help.381",
"callback": "options",
"hotkey": "o"
},
{
"type": "button",
"name": "surrender",
"position": {"x": 55, "y": 560},
"image": "icm001",
"help": "core.help.379",
"callback": "surrender",
"hotkey": "s"
},
{
"type": "button",
"name": "flee",
"position": {"x": 106, "y": 560},
"image": "icm002",
"help": "core.help.380",
"callback": "flee",
"hotkey": "r"
},
{
"type": "button",
"name": "autofight",
"position": {"x": 157, "y": 560},
"image": "icm004",
"help": "core.help.382",
"callback": "autofight",
"hotkey": "a"
},
{
"type": "button",
"name": "cast",
"position": {"x": 646, "y": 560},
"image": "icm005",
"help": "core.help.385",
"callback": "spellbook",
"hotkey": "c"
},
{
"type": "button",
"name": "wait",
"position": {"x": 697, "y": 560},
"image": "icm006",
"help": "core.help.386",
"callback": "wait",
"hotkey": "w"
},
{
"type": "button",
"name": "defence",
"position": {"x": 748, "y": 560},
"image": "icm007",
"help": "core.help.387",
"callback": "defence",
"hotkey": ["d", "space"]
},
{
"type": "button",
"name": "consoleUp",
"position": {"x": 625, "y": 560},
"image": "ComSlide",
"callback": "consoleUp",
"imageOrder": [0, 1, 0, 0],
"hotkey": "up"
},
{
"type": "button",
"name": "consoleDown",
"position": {"x": 625, "y": 579},
"image": "ComSlide",
"callback": "consoleDown",
"imageOrder": [2, 3, 2, 2],
"hotkey": "down"
},
{
"type": "battleConsole",
"name": "console",
"imagePosition" : { "x" : 214, "y" : 7 },
"rect": {"x": 215, "y": 562, "w": 400, "h": 32}
},
{
"type": "button",
"name": "tacticNext",
"position": {"x": 213, "y": 560},
"image": "icm011",
"callback": "tacticNext",
"hotkey": "space"
},
{
"type": "button",
"name": "tacticEnd",
"position": {"x": 419, "y": 560},
"image": "icm012",
"callback": "tacticEnd",
"hotkey": "enter"
}
]
}

View File

@ -14,7 +14,7 @@
"font": "big",
"alignment": "center",
"color": "yellow",
"text": 738,
"text": "core.genrltxt.738",
"position": {"x": 222, "y": 36}
},
@ -24,7 +24,7 @@
"font": "small",
"alignment": "center",
"color": "white",
"text": 739,
"text": "core.genrltxt.739",
"position": {"x": 222, "y": 56}
},
@ -34,7 +34,7 @@
"font": "small",
"alignment": "center",
"color": "white",
"text": 752,
"text": "core.genrltxt.752",
"position": {"x": 104, "y": 97}
},
@ -48,7 +48,7 @@
"index": 0,
"type": "toggleButton",
"image": "RANSIZS",
"zelp": 198,
"help": "core.help.198",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 0, "y": 0},
},
@ -56,7 +56,7 @@
{
"type": "toggleButton",
"image": "RANSIZM",
"zelp": 199,
"help": "core.help.199",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 47, "y": 0},
},
@ -64,7 +64,7 @@
{
"type": "toggleButton",
"image": "RANSIZL",
"zelp": 200,
"help": "core.help.200",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 94, "y": 0},
},
@ -72,7 +72,7 @@
{
"type": "toggleButton",
"image": "RANSIZX",
"zelp": 201,
"help": "core.help.201",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 141, "y": 0}
}
@ -100,56 +100,56 @@
"index": 1,
"type": "toggleButton",
"image": "RANNUM1",
"zelp": 204,
"help": "core.help.204",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 0, "y": 0}
},
{
"type": "toggleButton",
"image": "RANNUM2",
"zelp": 205,
"help": "core.help.205",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 32, "y": 0}
},
{
"type": "toggleButton",
"image": "RANNUM3",
"zelp": 206,
"help": "core.help.206",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 64, "y": 0}
},
{
"type": "toggleButton",
"image": "RANNUM4",
"zelp": 207,
"help": "core.help.207",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 96, "y": 0}
},
{
"type": "toggleButton",
"image": "RANNUM5",
"zelp": 208,
"help": "core.help.208",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 128, "y": 0}
},
{
"type": "toggleButton",
"image": "RANNUM6",
"zelp": 209,
"help": "core.help.209",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 160, "y": 0}
},
{
"type": "toggleButton",
"image": "RANNUM7",
"zelp": 210,
"help": "core.help.210",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 192, "y": 0}
},
{
"type": "toggleButton",
"image": "RANNUM8",
"zelp": 211,
"help": "core.help.211",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 224, "y": 0}
},
@ -157,7 +157,7 @@
"index": -1,
"type": "toggleButton",
"image": "RANRAND",
"zelp": 212,
"help": "core.help.212",
"position": {"x": 256, "y": 0},
}
],
@ -175,56 +175,56 @@
"index": 0,
"type": "toggleButton",
"image": "RANNUM0",
"zelp": 214,
"help": "core.help.214",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 0, "y": 0}
},
{
"type": "toggleButton",
"image": "RANNUM1",
"zelp": 215,
"help": "core.help.215",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 32, "y": 0}
},
{
"type": "toggleButton",
"image": "RANNUM2",
"zelp": 216,
"help": "core.help.216",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 64, "y": 0}
},
{
"type": "toggleButton",
"image": "RANNUM3",
"zelp": 217,
"help": "core.help.217",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 96, "y": 0}
},
{
"type": "toggleButton",
"image": "RANNUM4",
"zelp": 218,
"help": "core.help.218",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 128, "y": 0}
},
{
"type": "toggleButton",
"image": "RANNUM5",
"zelp": 219,
"help": "core.help.219",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 160, "y": 0}
},
{
"type": "toggleButton",
"image": "RANNUM6",
"zelp": 220,
"help": "core.help.220",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 192, "y": 0}
},
{
"type": "toggleButton",
"image": "RANNUM7",
"zelp": 221,
"help": "core.help.221",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 224, "y": 0}
},
@ -232,7 +232,7 @@
"index": -1,
"type": "toggleButton",
"image": "RANRAND",
"zelp": 222,
"help": "core.help.222",
"position": {"x": 256, "y": 0},
}
],
@ -250,56 +250,56 @@
"index": 0,
"type": "toggleButton",
"image": "RANNUM0",
"zelp": 224,
"help": "core.help.224",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 0, "y": 0}
},
{
"type": "toggleButton",
"image": "RANNUM1",
"zelp": 225,
"help": "core.help.225",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 32, "y": 0}
},
{
"type": "toggleButton",
"image": "RANNUM2",
"zelp": 226,
"help": "core.help.226",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 64, "y": 0}
},
{
"type": "toggleButton",
"image": "RANNUM3",
"zelp": 227,
"help": "core.help.227",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 96, "y": 0}
},
{
"type": "toggleButton",
"image": "RANNUM4",
"zelp": 228,
"help": "core.help.228",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 128, "y": 0}
},
{
"type": "toggleButton",
"image": "RANNUM5",
"zelp": 229,
"help": "core.help.229",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 160, "y": 0}
},
{
"type": "toggleButton",
"image": "RANNUM6",
"zelp": 230,
"help": "core.help.230",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 192, "y": 0}
},
{
"type": "toggleButton",
"image": "RANNUM7",
"zelp": 231,
"help": "core.help.231",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 224, "y": 0}
},
@ -307,7 +307,7 @@
"index": -1,
"type": "toggleButton",
"image": "RANRAND",
"zelp": 232,
"help": "core.help.232",
"position": {"x": 256, "y": 0},
}
],
@ -325,49 +325,49 @@
"index": 0,
"type": "toggleButton",
"image": "RANNUM0",
"zelp": 234,
"help": 234,
"imageOrder": [0, 1, 1, 3],
"position": {"x": 0, "y": 0}
},
{
"type": "toggleButton",
"image": "RANNUM1",
"zelp": 235,
"help": "core.help.235",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 32, "y": 0}
},
{
"type": "toggleButton",
"image": "RANNUM2",
"zelp": 236,
"help": "core.help.236",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 64, "y": 0}
},
{
"type": "toggleButton",
"image": "RANNUM3",
"zelp": 237,
"help": "core.help.237",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 96, "y": 0}
},
{
"type": "toggleButton",
"image": "RANNUM4",
"zelp": 238,
"help": "core.help.238",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 128, "y": 0}
},
{
"type": "toggleButton",
"image": "RANNUM5",
"zelp": 239,
"help": "core.help.239",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 160, "y": 0}
},
{
"type": "toggleButton",
"image": "RANNUM6",
"zelp": 240,
"help": "core.help.240",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 192, "y": 0}
},
@ -375,7 +375,7 @@
"index": -1,
"type": "toggleButton",
"image": "RANRAND",
"zelp": 241,
"help": "core.help.241",
"position": {"x": 256, "y": 0},
}
],
@ -393,21 +393,21 @@
"index": 0,
"type": "toggleButton",
"image": "RANNONE",
"zelp": 243,
"help": "core.help.243",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 0, "y": 0}
},
{
"type": "toggleButton",
"image": "RANNORM",
"zelp": 244,
"help": "core.help.244",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 85, "y": 0}
},
{
"type": "toggleButton",
"image": "RANISLD",
"zelp": 245,
"help": "core.help.245",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 170, "y": 0}
},
@ -415,7 +415,7 @@
"index": -1,
"type": "toggleButton",
"image": "RANRAND",
"zelp": 246,
"help": "core.help.246",
"position": {"x": 256, "y": 0},
}
],
@ -433,7 +433,7 @@
"index": 2,
"type": "toggleButton",
"image": "RANWEAK",
"zelp": 248,
"help": "core.help.248",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 0, "y": 0}
},
@ -441,7 +441,7 @@
"index": 3,
"type": "toggleButton",
"image": "RANNORM",
"zelp": 249,
"help": "core.help.249",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 85, "y": 0}
},
@ -449,7 +449,7 @@
"index": 4,
"type": "toggleButton",
"image": "RANSTRG",
"zelp": 250,
"help": "core.help.250",
"imageOrder": [0, 1, 1, 3],
"position": {"x": 170, "y": 0}
},
@ -457,7 +457,7 @@
"index": -2,
"type": "toggleButton",
"image": "RANRAND",
"zelp": 251,
"help": "core.help.251",
"position": {"x": 256, "y": 0},
}
],
@ -470,7 +470,7 @@
"type": "button",
"position": {"x": 54, "y": 535},
"image": "RANSHOW",
"zelp": 252
"help": "core.help.252"
},
{
@ -482,27 +482,27 @@
[
{
"position": {"x": 68, "y": 133},
"text": 753
"text": "core.genrltxt.753"
},
{
"position": {"x": 68, "y": 199},
"text": 754
"text": "core.genrltxt.754"
},
{
"position": {"x": 68, "y": 265},
"text": 755
"text": "core.genrltxt.755"
},
{
"position": {"x": 68, "y": 331},
"text": 756
"text": "core.genrltxt.756"
},
{
"position": {"x": 68, "y": 398},
"text": 757
"text": "core.genrltxt.757"
},
{
"position": {"x": 68, "y": 465},
"text": 758
"text": "core.genrltxt.758"
}
]
}

View File

@ -174,9 +174,9 @@ void CAdventureAI::battleStart(const CCreatureSet * army1, const CCreatureSet *
battleAI->battleStart(army1, army2, tile, hero1, hero2, side);
}
void CAdventureAI::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa)
void CAdventureAI::battleStacksAttacked(const std::vector<BattleStackAttacked> & bsa, bool ranged)
{
battleAI->battleStacksAttacked(bsa);
battleAI->battleStacksAttacked(bsa, ranged);
}
void CAdventureAI::actionStarted(const BattleAction & action)
@ -204,9 +204,9 @@ void CAdventureAI::battleObstaclesChanged(const std::vector<ObstacleChanges> & o
battleAI->battleObstaclesChanged(obstacles);
}
void CAdventureAI::battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance)
void CAdventureAI::battleStackMoved(const CStack * stack, std::vector<BattleHex> dest, int distance, bool teleport)
{
battleAI->battleStackMoved(stack, dest, distance);
battleAI->battleStackMoved(stack, dest, distance, teleport);
}
void CAdventureAI::battleAttack(const BattleAttack * ba)
@ -225,9 +225,9 @@ void CAdventureAI::battleEnd(const BattleResult * br)
battleAI.reset();
}
void CAdventureAI::battleUnitsChanged(const std::vector<UnitChanges> & units, const std::vector<CustomEffectInfo> & customEffects)
void CAdventureAI::battleUnitsChanged(const std::vector<UnitChanges> & units)
{
battleAI->battleUnitsChanged(units, customEffects);
battleAI->battleUnitsChanged(units);
}
BattleAction CAdventureAI::activeStack(const CStack * stack)

Some files were not shown because too many files have changed in this diff Show More