1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-28 08:48:48 +02:00

Merge branch 'develop' of https://github.com/vcmi/vcmi into develop

Trying to sort out git tree.
This commit is contained in:
DJWarmonger 2017-06-16 21:15:46 +02:00
commit 5a31cc831d
44 changed files with 524 additions and 352 deletions

View File

@ -1,52 +1,36 @@
language: cpp
os:
- linux
- osx
- linux
- osx
dist: trusty
sudo: required
before_install:
- test $TRAVIS_BRANCH != coverity_scan -o ${TRAVIS_JOB_NUMBER##*.} = 1 || exit 0
- if [[ $VCMI_PLATFORM == 'linux' ]]; then . .travis.linux; fi
- if [[ $VCMI_PLATFORM == 'mac' ]]; then . .travis.osx; fi
- if [[ $VCMI_PLATFORM == 'mxe' ]]; then . .travis.mxe; fi
before_script:
- mkdir build
- cd build
- if [[ $TRAVIS_BRANCH != 'coverity_scan' ]]; then cmake -G "Unix Makefiles" .. $VCMI_CMAKE_FLAGS; fi
script:
- test $TRAVIS_BRANCH != coverity_scan || exit 0
- if [[ $TRAVIS_OS_NAME == 'osx' ]]; then cd ..; xcodebuild -project osx/osx-vcmibuilder/vcmibuilder.xcodeproj/ -configuration Release CONFIGURATION_BUILD_DIR=..; cd build; fi
- make -j2
env:
matrix:
- ignore=this
global:
# The next declaration is the encrypted COVERITY_SCAN_TOKEN, created
# via the "travis encrypt" command using the project repo's public key
- secure: "NMg+qtQB4DIZ/KqlDeIn3K7A7Ydksdpnbv6Ha9n4bSSA0AT8wlPwbHXvQmiR8qYs6cnz4fyY6NVcBe7X3bdR8jWyPNAS0l0QByqG12q3dBpEtNNn0X5u/GS3wHse5+ObNAF9a83+xACTQj2UdxqHgJ3LFGzdBpQt3kLsc8NDnn8="
- secure: NMg+qtQB4DIZ/KqlDeIn3K7A7Ydksdpnbv6Ha9n4bSSA0AT8wlPwbHXvQmiR8qYs6cnz4fyY6NVcBe7X3bdR8jWyPNAS0l0QByqG12q3dBpEtNNn0X5u/GS3wHse5+ObNAF9a83+xACTQj2UdxqHgJ3LFGzdBpQt3kLsc8NDnn8=
matrix:
exclude:
- env: ignore=this
- env: ignore=this
include:
- os: linux
compiler: clang
env: VCMI_PLATFORM='linux' REAL_CC=clang-3.6 REAL_CXX=clang++-3.6 PACKAGE=clang-3.6 SUPPORT=libstdc++-4.8-dev VCMI_CMAKE_FLAGS='-DENABLE_TEST=0'
- os: linux
compiler: clang
env: VCMI_PLATFORM='linux' REAL_CC=clang-3.4 REAL_CXX=clang++-3.4 PACKAGE=clang-3.4 SUPPORT=libstdc++-4.8-dev VCMI_CMAKE_FLAGS='-DENABLE_TEST=0'
- os: linux
compiler: gcc
env: VCMI_PLATFORM='linux' REAL_CC=gcc-4.8 REAL_CXX=g++-4.8 PACKAGE=g++-4.8 SUPPORT= VCMI_CMAKE_FLAGS='-DENABLE_TEST=0'
- os: linux
env: VCMI_PLATFORM='mxe' MXE_TARGET=i686-w64-mingw32.shared VCMI_CMAKE_FLAGS='-DENABLE_TEST=0'
sudo: required
- os: osx
env: VCMI_PLATFORM='mac'
- os: linux
compiler: clang
env: VCMI_PLATFORM='linux' REAL_CC=clang-3.6 REAL_CXX=clang++-3.6 PACKAGE=clang-3.6
SUPPORT=libstdc++-4.8-dev VCMI_CMAKE_FLAGS='-DENABLE_TEST=0'
- os: linux
compiler: clang
env: VCMI_PLATFORM='linux' REAL_CC=clang-3.4 REAL_CXX=clang++-3.4 PACKAGE=clang-3.4
SUPPORT=libstdc++-4.8-dev VCMI_CMAKE_FLAGS='-DENABLE_TEST=0'
- os: linux
compiler: gcc
env: VCMI_PLATFORM='linux' REAL_CC=gcc-4.8 REAL_CXX=g++-4.8 PACKAGE=g++-4.8 SUPPORT=
VCMI_CMAKE_FLAGS='-DENABLE_TEST=0'
- os: linux
env: VCMI_PLATFORM='mxe' MXE_TARGET=i686-w64-mingw32.shared VCMI_CMAKE_FLAGS='-DENABLE_TEST=0'
sudo: required
- os: osx
env: VCMI_PLATFORM='mac'
addons:
coverity_scan:
@ -54,15 +38,47 @@ addons:
name: vcmi/vcmi
description: Build submitted via Travis CI
notification_email: coverity@arseniyshestakov.com
build_command_prepend: "cov-configure --compiler clang-3.6 --comptype clangcc && cov-configure --comptype clangcxx --compiler clang++-3.6 && cmake -G Ninja .. -DCMAKE_BUILD_TYPE=DEBUG -DENABLE_LAUNCHER=0 -DENABLE_TEST=0"
build_command_prepend: cov-configure --compiler clang-3.6 --comptype clangcc &&
cov-configure --comptype clangcxx --compiler clang++-3.6 && cmake -G Ninja ..
-DCMAKE_BUILD_TYPE=DEBUG -DENABLE_LAUNCHER=0 -DENABLE_TEST=0
build_command: ninja -j 3
branch_pattern: coverity_scan
notifications:
email:
recipients:
- noreply@vcmi.eu
- noreply@vcmi.eu
on_success: change
on_failure: always
slack:
secure: "KHXFe14FFKtw5mErWbj730+utqy7i/3AUobWfAMAGvWI5sJYlhbBU+KvvCoD2SlRQg3mQqgwVw8NBJF1Mffs7WcRmrFFFmuMqZxFLAfKBd3T0CxWpAGfnfNgDmlfV4OfEgQWk1pakEPOymhxbbmLUuCjykZDuTcioxAk0UAHDwY="
secure: KHXFe14FFKtw5mErWbj730+utqy7i/3AUobWfAMAGvWI5sJYlhbBU+KvvCoD2SlRQg3mQqgwVw8NBJF1Mffs7WcRmrFFFmuMqZxFLAfKBd3T0CxWpAGfnfNgDmlfV4OfEgQWk1pakEPOymhxbbmLUuCjykZDuTcioxAk0UAHDwY=
before_install:
- test $TRAVIS_BRANCH != coverity_scan -o ${TRAVIS_JOB_NUMBER##*.} = 1 || exit 0
- . $TRAVIS_BUILD_DIR/CI/$VCMI_PLATFORM/before_install.sh
before_script:
- mkdir build
- cd build
- if [[ $TRAVIS_BRANCH != 'coverity_scan' ]];
then
source $TRAVIS_BUILD_DIR/CI/get_package_name.sh;
cmake -G "Unix Makefiles" .. $VCMI_CMAKE_FLAGS
-DPACKAGE_NAME_SUFFIX:STRING="$VCMI_PACKAGE_NAME_SUFFIX"
-DPACKAGE_FILE_NAME:STRING="$VCMI_PACKAGE_FILE_NAME";
fi
script:
- test $TRAVIS_BRANCH != coverity_scan || exit 0
- if [[ $TRAVIS_OS_NAME == 'osx' ]];
then
cd ..;
xcodebuild -project osx/osx-vcmibuilder/vcmibuilder.xcodeproj/
-configuration Release CONFIGURATION_BUILD_DIR=..;
cd build;
fi
- make -j2
after_success:
- test $TRAVIS_BRANCH != coverity_scan || exit 0
- . $TRAVIS_BUILD_DIR/CI/$VCMI_PLATFORM/upload_package.sh

View File

@ -259,11 +259,9 @@ void CBattleAI::attemptCastingSpell()
{
StackWithBonuses swb;
swb.stack = sta;
Bonus pseudoBonus;
pseudoBonus.sid = ps.spell->id;
pseudoBonus.val = skillLevel;
pseudoBonus.turnsRemain = 1; //TODO
CStack::stackEffectToFeature(swb.bonusesToAdd, pseudoBonus);
//todo: handle effect actualization in HypotheticChangesToBattleState
ps.spell->getEffects(swb.bonusesToAdd, skillLevel, false, hero->getEnchantPower(ps.spell));
ps.spell->getEffects(swb.bonusesToAdd, skillLevel, true, hero->getEnchantPower(ps.spell));
HypotheticChangesToBattleState state;
state.bonusesOfStacks[swb.stack] = &swb;
PotentialTargets pt(swb.stack, state);

View File

@ -746,7 +746,7 @@ void VCAI::makeTurn()
logGlobal->info("Player %d (%s) starting turn", playerID, playerID.getStr());
MAKING_TURN;
boost::shared_lock<boost::shared_mutex> gsLock(cb->getGsMutex());
boost::shared_lock<boost::shared_mutex> gsLock(CGameState::mutex);
setThreadName("VCAI::makeTurn");
switch(cb->getDate(Date::DAY_OF_WEEK))
@ -1688,7 +1688,7 @@ void VCAI::battleEnd(const BattleResult *br)
void VCAI::waitTillFree()
{
auto unlock = vstd::makeUnlockSharedGuard(cb->getGsMutex());
auto unlock = vstd::makeUnlockSharedGuard(CGameState::mutex);
status.waitTillFree();
}
@ -2787,7 +2787,7 @@ void VCAI::requestActionASAP(std::function<void()> whatToDo)
{
setThreadName("VCAI::requestActionASAP::whatToDo");
SET_GLOBAL_STATE(this);
boost::shared_lock<boost::shared_mutex> gsLock(cb->getGsMutex());
boost::shared_lock<boost::shared_mutex> gsLock(CGameState::mutex);
whatToDo();
});
}

View File

@ -180,7 +180,7 @@ int CBattleCallback::sendRequest(const CPack *request)
if(waitTillRealize)
{
logGlobal->traceStream() << boost::format("We'll wait till request %d is answered.\n") % requestID;
auto gsUnlocker = vstd::makeUnlockSharedGuardIf(getGsMutex(), unlockGsWhenWaiting);
auto gsUnlocker = vstd::makeUnlockSharedGuardIf(CGameState::mutex, unlockGsWhenWaiting);
cl->waitingRequest.waitWhileContains(requestID);
}

BIN
CI/deploy_rsa.enc Normal file

Binary file not shown.

16
CI/get_package_name.sh Normal file
View File

@ -0,0 +1,16 @@
#!/bin/sh
VCMI_PACKAGE_FILE_NAME="${TRAVIS_JOB_ID}-vcmi"
VCMI_PACKAGE_NAME_SUFFIX=""
if [ "$TRAVIS_PULL_REQUEST" = "false" ];
then
branch_name=$(echo "$TRAVIS_BRANCH" | sed 's/[^[:alnum:]]\+/_/g')
VCMI_PACKAGE_FILE_NAME="${VCMI_PACKAGE_FILE_NAME}-branch-${branch_name}-${TRAVIS_COMMIT}"
VCMI_PACKAGE_NAME_SUFFIX="branch ${branch_name}"
else
VCMI_PACKAGE_FILE_NAME="${VCMI_PACKAGE_FILE_NAME}-PR-${TRAVIS_PULL_REQUEST}-${TRAVIS_COMMIT}"
VCMI_PACKAGE_NAME_SUFFIX="PR ${TRAVIS_PULL_REQUEST}"
fi
VCMI_PACKAGE_NAME_SUFFIX="(${VCMI_PACKAGE_NAME_SUFFIX})"
export VCMI_PACKAGE_FILE_NAME
export VCMI_PACKAGE_NAME_SUFFIX

View File

@ -0,0 +1 @@
#!/bin/sh

View File

@ -14,4 +14,4 @@ wget https://github.com/sparkle-project/Sparkle/releases/download/$sparkle_versi
mkdir sparkle && cd sparkle
tar -xf ../Sparkle-*.tar.bz2
sudo mv Sparkle.framework /Library/Frameworks/
cd .. && rm -rf sparkle
cd .. && rm -rf sparkle

10
CI/mac/upload_package.sh Normal file
View File

@ -0,0 +1,10 @@
#!/bin/sh
cpack
touch /tmp/deploy_rsa
chmod 600 /tmp/deploy_rsa
openssl aes-256-cbc -K $encrypted_1d30f79f8582_key -iv $encrypted_1d30f79f8582_iv -in $TRAVIS_BUILD_DIR/CI/deploy_rsa.enc -out /tmp/deploy_rsa -d
eval "$(ssh-agent -s)"
ssh-add /tmp/deploy_rsa
sftp -r -o StrictHostKeyChecking=no travis@beholder.vcmi.eu <<< "put $VCMI_PACKAGE_FILE_NAME.dmg /incoming/$VCMI_PACKAGE_FILE_NAME.dmg"

View File

@ -22,6 +22,9 @@ mxe-$MXE_TARGET-ffmpeg \
mxe-$MXE_TARGET-qt \
mxe-$MXE_TARGET-qtbase
# Install nsis for installer creation
sudo apt-get install -qq nsis
# alias for CMake
sudo mv /usr/bin/cmake /usr/bin/cmake.orig
sudo ln -s /usr/lib/mxe/usr/bin/$MXE_TARGET-cmake /usr/bin/cmake

10
CI/mxe/upload_package.sh Normal file
View File

@ -0,0 +1,10 @@
#!/bin/sh
cpack
touch /tmp/deploy_rsa
chmod 600 /tmp/deploy_rsa
openssl aes-256-cbc -K $encrypted_1d30f79f8582_key -iv $encrypted_1d30f79f8582_iv -in $TRAVIS_BUILD_DIR/CI/deploy_rsa.enc -out /tmp/deploy_rsa -d
eval "$(ssh-agent -s)"
ssh-add /tmp/deploy_rsa
sftp -r -o StrictHostKeyChecking=no travis@beholder.vcmi.eu <<< "put $VCMI_PACKAGE_FILE_NAME.exe /incoming/$VCMI_PACKAGE_FILE_NAME.exe"

View File

@ -16,6 +16,10 @@ set(VCMI_VERSION_MAJOR 0)
set(VCMI_VERSION_MINOR 99)
set(VCMI_VERSION_PATCH 0)
# Allow to pass package name from Travis CI
set(PACKAGE_NAME_SUFFIX "" CACHE STRING "Suffix for CPack package name")
set(PACKAGE_FILE_NAME "" CACHE STRING "Override for CPack package filename")
option(ENABLE_ERM "Enable compilation of ERM scripting module" OFF)
option(ENABLE_EDITOR "Enable compilation of map editor" OFF)
option(ENABLE_LAUNCHER "Enable compilation of launcher" ON)
@ -238,14 +242,17 @@ endif()
# For apple these files will be already inside vcmiclient bundle
if (NOT APPLE)
# copy whole directory but .svn control files
install(DIRECTORY config DESTINATION ${DATA_DIR} PATTERN ".svn" EXCLUDE)
install(DIRECTORY config DESTINATION ${DATA_DIR})
# copy vcmi mod along with all its content
install(DIRECTORY Mods/vcmi DESTINATION ${DATA_DIR}/Mods PATTERN ".svn" EXCLUDE)
install(DIRECTORY Mods/vcmi DESTINATION ${DATA_DIR}/Mods)
install(FILES vcmibuilder DESTINATION ${BIN_DIR} PERMISSIONS
OWNER_WRITE OWNER_READ OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE)
# that script is useless for Windows
if (NOT WIN32)
install(FILES vcmibuilder DESTINATION ${BIN_DIR} PERMISSIONS
OWNER_WRITE OWNER_READ OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE)
endif()
endif()
if(WIN32)
@ -317,15 +324,28 @@ set(CPACK_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSIO
set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY 0)
if("${PACKAGE_NAME_SUFFIX}" STREQUAL "")
set(CPACK_PACKAGE_NAME "VCMI")
else()
set(CPACK_PACKAGE_NAME "VCMI ${PACKAGE_NAME_SUFFIX}")
endif()
if("${PACKAGE_FILE_NAME}" STREQUAL "")
set(CPACK_PACKAGE_FILE_NAME "vcmi-${CPACK_PACKAGE_VERSION}")
else()
set(CPACK_PACKAGE_FILE_NAME "${PACKAGE_FILE_NAME}")
endif()
set(CPACK_PACKAGE_VENDOR "VCMI team")
if(WIN32)
set(CPACK_MONOLITHIC_INSTALL 1)
set(CPACK_PACKAGE_NAME "VCMI")
set(CPACK_PACKAGE_VENDOR "VCMI team")
set(CPACK_PACKAGE_FILE_NAME "vcmi-${CPACK_PACKAGE_VERSION}-win32")
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/license.txt")
set(CPACK_PACKAGE_EXECUTABLES "VCMI_launcher;VCMI")
set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CPACK_PACKAGE_NAME}")
set(CPACK_NSIS_PACKAGE_NAME "VCMI ${CPACK_PACKAGE_VERSION}")
if("${PACKAGE_NAME_SUFFIX}" STREQUAL "")
set(CPACK_NSIS_PACKAGE_NAME "VCMI ${CPACK_PACKAGE_VERSION}")
else()
set(CPACK_NSIS_PACKAGE_NAME "VCMI ${CPACK_PACKAGE_VERSION} ${PACKAGE_NAME_SUFFIX} ")
endif()
set(CPACK_NSIS_INSTALL_ROOT "$PROGRAMFILES")
set(CPACK_NSIS_EXTRA_INSTALL_COMMANDS " CreateShortCut \\\"$DESKTOP\\\\VCMI.lnk\\\" \\\"$INSTDIR\\\\VCMI_launcher.exe\\\"")
set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS " Delete \\\"$DESKTOP\\\\VCMI.lnk\\\" ")
@ -340,8 +360,9 @@ else()
set(CPACK_GENERATOR TGZ)
endif()
INCLUDE(CPack)
include(GetGitRevisionDescription)
get_git_head_revision(GIT_REFSPEC GIT_SHA1)
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/Version.cpp.in" "${CMAKE_BINARY_DIR}/Version.cpp" @ONLY)
INCLUDE(CPack)

View File

@ -3,6 +3,9 @@
GENERAL:
* Spectator mode was implemented through command-line options
SPELLS:
* Implemented cumulative effects for spells
0.98 -> 0.99
GENERAL:

View File

@ -123,7 +123,6 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player)
currentSelection = nullptr;
castleInt = nullptr;
battleInt = nullptr;
//pim = new boost::recursive_mutex;
makingTurn = false;
showingDialog = new CondSh<bool>(false);
cingconsole = new CInGameConsole;
@ -140,8 +139,6 @@ CPlayerInterface::~CPlayerInterface()
{
logGlobal->traceStream() << "\tHuman player interface for player " << playerID << " being destructed";
//howManyPeople--;
//delete pim;
//vstd::clear_pointer(pim);
delete showingDialog;
delete cingconsole;
if (LOCPLINT == this)
@ -821,10 +818,10 @@ BattleAction CPlayerInterface::activeStack(const CStack * stack) //called when i
CBattleInterface *b = battleInt;
if (b->givenCommand->get())
if(CBattleInterface::givenCommand.get())
{
logGlobal->errorStream() << "Command buffer must be clean! (we don't want to use old command)";
vstd::clear_pointer(b->givenCommand->data);
vstd::clear_pointer(CBattleInterface::givenCommand.data);
}
{
@ -833,17 +830,17 @@ BattleAction CPlayerInterface::activeStack(const CStack * stack) //called when i
//Regeneration & mana drain go there
}
//wait till BattleInterface sets its command
boost::unique_lock<boost::mutex> lock(b->givenCommand->mx);
while(!b->givenCommand->data)
boost::unique_lock<boost::mutex> lock(CBattleInterface::givenCommand.mx);
while(!CBattleInterface::givenCommand.data)
{
b->givenCommand->cond.wait(lock);
CBattleInterface::givenCommand.cond.wait(lock);
if (!battleInt) //battle ended while we were waiting for movement (eg. because of spell)
throw boost::thread_interrupted(); //will shut the thread peacefully
}
//tidy up
BattleAction ret = *(b->givenCommand->data);
vstd::clear_pointer(b->givenCommand->data);
BattleAction ret = *(CBattleInterface::givenCommand.data);
vstd::clear_pointer(CBattleInterface::givenCommand.data);
if (ret.actionType == Battle::CANCEL)
{
@ -1637,7 +1634,7 @@ void CPlayerInterface::setSelection(const CArmedInstance * obj)
void CPlayerInterface::update()
{
// Make sure that gamestate won't change when GUI objects may obtain its parts on event processing or drawing request
boost::shared_lock<boost::shared_mutex> gsLock(cb->getGsMutex());
boost::shared_lock<boost::shared_mutex> gsLock(CGameState::mutex);
// While mutexes were locked away we may be have stopped being the active interface
if (LOCPLINT != this)

View File

@ -47,6 +47,7 @@
*/
CondSh<bool> CBattleInterface::animsAreDisplayed(false);
CondSh<BattleAction *> CBattleInterface::givenCommand(nullptr);
static void onAnimationFinished(const CStack *stack, CCreatureAnimation *anim)
{
@ -101,7 +102,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
currentlyHoveredHex(-1), attackingHex(-1), stackCanCastSpell(false), creatureCasting(false), spellDestSelectMode(false), spellToCast(nullptr), sp(nullptr),
creatureSpellToCast(-1),
siegeH(nullptr), attackerInt(att), defenderInt(defen), curInt(att), animIDhelper(0),
givenCommand(nullptr), myTurn(false), resWindow(nullptr), moveStarted(false), moveSoundHander(-1), bresult(nullptr)
myTurn(false), resWindow(nullptr), moveStarted(false), moveSoundHander(-1), bresult(nullptr)
{
OBJ_CONSTRUCTION;
@ -117,7 +118,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
animsAreDisplayed.setn(false);
pos = myRect;
strongInterest = true;
givenCommand = new CondSh<BattleAction *>(nullptr);
givenCommand.setn(nullptr);
//hot-seat -> check tactics for both players (defender may be local human)
if (attackerInt && attackerInt->cb->battleGetTacticDist())
@ -387,8 +388,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
CBattleInterface::~CBattleInterface()
{
curInt->battleInt = nullptr;
givenCommand->cond.notify_all(); //that two lines should make any activeStack waiting thread to finish
givenCommand.cond.notify_all(); //that two lines should make any activeStack waiting thread to finish
if (active) //dirty fix for #485
{
@ -416,7 +416,6 @@ CBattleInterface::~CBattleInterface()
delete bConsoleUp;
delete bConsoleDown;
delete console;
delete givenCommand;
delete attackingHero;
delete defendingHero;
@ -1028,7 +1027,7 @@ void CBattleInterface::stackRemoved(int stackID)
action->side = defendingHeroInstance ? (curInt->playerID == defendingHeroInstance->tempOwner) : false;
action->actionType = Battle::CANCEL;
action->stackNumber = activeStack->ID;
givenCommand->setn(action);
givenCommand.setn(action);
setActiveStack(nullptr);
}
}
@ -1149,7 +1148,7 @@ void CBattleInterface::giveCommand(Battle::ActionType action, BattleHex tile, ui
logGlobal->traceStream() << "Setting command for " << (stack ? stack->nodeName() : "hero");
myTurn = false;
setActiveStack(nullptr);
givenCommand->setn(ba);
givenCommand.setn(ba);
}
else
{
@ -1359,52 +1358,44 @@ void CBattleInterface::spellCast(const BattleSpellCast *sc)
void CBattleInterface::battleStacksEffectsSet(const SetStackEffect & sse)
{
if (sse.effect.back().sid == -1 && sse.stacks.size() == 1 && sse.effect.size() == 2)
if(sse.stacks.size() == 1 && sse.effect.size() == 2 && sse.effect.back().sid == -1)
{
const Bonus & bns = sse.effect.front();
if (bns.source == Bonus::OTHER && bns.type == Bonus::PRIMARY_SKILL)
if(bns.source == Bonus::OTHER && bns.type == Bonus::PRIMARY_SKILL)
{
//defensive stance
const CStack *stack = LOCPLINT->cb->battleGetStackByID(*sse.stacks.begin());
int txtid = 120;
if (stack->count != 1)
if(stack->count != 1)
txtid++; //move to plural text
BonusList defenseBonuses = *(stack->getBonuses(Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE)));
defenseBonuses.remove_if (Bonus::UntilGetsTurn); //remove bonuses gained from defensive stance
defenseBonuses.remove_if(Bonus::UntilGetsTurn); //remove bonuses gained from defensive stance
int val = stack->Defense() - defenseBonuses.totalValue();
auto txt = boost::format (CGI->generaltexth->allTexts[txtid]) % ((stack->count != 1) ? stack->getCreature()->namePl : stack->getCreature()->nameSing) % val;
auto txt = boost::format(CGI->generaltexth->allTexts[txtid]) % ((stack->count != 1) ? stack->getCreature()->namePl : stack->getCreature()->nameSing) % val;
console->addText(boost::to_string(txt));
}
}
if (activeStack != nullptr) //it can be -1 when a creature casts effect
{
if(activeStack != nullptr)
redrawBackgroundWithHexes(activeStack);
}
}
CBattleInterface::PossibleActions CBattleInterface::getCasterAction(const CSpell *spell, const ISpellCaster *caster, ECastingMode::ECastingMode mode) const
CBattleInterface::PossibleActions CBattleInterface::getCasterAction(const CSpell * spell, const ISpellCaster * caster, ECastingMode::ECastingMode mode) const
{
PossibleActions spellSelMode = ANY_LOCATION;
const CSpell::TargetInfo ti(spell, caster->getSpellSchoolLevel(spell), mode);
if (ti.massive || ti.type == CSpell::NO_TARGET)
if(ti.massive || ti.type == CSpell::NO_TARGET)
spellSelMode = NO_LOCATION;
else if (ti.type == CSpell::LOCATION && ti.clearAffected)
{
else if(ti.type == CSpell::LOCATION && ti.clearAffected)
spellSelMode = FREE_LOCATION;
}
else if (ti.type == CSpell::CREATURE)
{
else if(ti.type == CSpell::CREATURE)
spellSelMode = AIMED_SPELL_CREATURE;
}
else if (ti.type == CSpell::OBSTACLE)
{
else if(ti.type == CSpell::OBSTACLE)
spellSelMode = OBSTACLE;
}
return spellSelMode;
}
@ -2848,7 +2839,7 @@ void CBattleInterface::requestAutofightingAIToTakeAction()
}
else
{
givenCommand->setn(ba.release());
givenCommand.setn(ba.release());
}
}
else

View File

@ -264,10 +264,13 @@ private:
PossibleActions getCasterAction(const CSpell *spell, const ISpellCaster *caster, ECastingMode::ECastingMode mode) const;
public:
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
std::list<std::pair<CBattleAnimation *, bool>> pendingAnims; //currently displayed animations <anim, initialized>
void addNewAnim(CBattleAnimation *anim); //adds new anim to pendingAnims
ui32 animIDhelper; //for giving IDs for animations
static CondSh<bool> animsAreDisplayed; //for waiting with the end of battle for end of anims
CBattleInterface(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); //c-tor
virtual ~CBattleInterface(); //d-tor
@ -282,7 +285,7 @@ public:
std::vector<CClickableHex*> bfield; //11 lines, 17 hexes on each
SDL_Surface *cellBorder, *cellShade;
CondSh<BattleAction *> *givenCommand; //data != nullptr if we have i.e. moved current unit
bool myTurn; //if true, interface is active (commands can be ordered)
CBattleResultWindow *resWindow; //window of end of battle

View File

@ -19,25 +19,25 @@
{
//assumed verticalPosition: top
"type": "string",
"format": "defFile"
"format": "defFile"
},
{
"type": "object",
"properties":{
"verticalPosition": {"type":"string", "enum":["top","bottom"]},
"defName": {"type":"string", "format": "defFile"}
},
"additionalProperties" : false
},
"additionalProperties" : false
}
]
}
]
}
},
"animation":{
"type": "object",
"additionalProperties" : false,
"properties":{
"affect":{"$ref" : "#/definitions/animationQueue"},
"hit":{"$ref" : "#/definitions/animationQueue"},
"affect":{"$ref" : "#/definitions/animationQueue"},
"hit":{"$ref" : "#/definitions/animationQueue"},
"cast":{"$ref" : "#/definitions/animationQueue"},
"projectile":{
"type":"array",
@ -46,11 +46,11 @@
"properties":{
"minimumAngle": {"type":"number", "minimum" : 0},
"defName": {"type":"string", "format": "defFile"}
},
"additionalProperties" : false
},
"additionalProperties" : false
}
}
}
}
}
},
"flags" :{
"type" : "object",
@ -85,7 +85,14 @@
},
"effects":{
"type": "object",
"description": "Timed effects",
"description": "Timed effects (updated by prolongation)",
"additionalProperties" : {
"$ref" : "vcmi:bonus"
}
},
"cumulativeEffects":{
"type": "object",
"description": "Timed effects (updated by unique bonus)",
"additionalProperties" : {
"$ref" : "vcmi:bonus"
}
@ -107,16 +114,16 @@
{
"type": "boolean",
"description": "LOCATION target only. All affected hexes/tile must be clear"
}
}
}
}
}
},
"texts":{
"type": "object",
"additionalProperties" : false
}
},
@ -239,9 +246,9 @@
"$ref" : "#/definitions/flags",
"description": "flags structure of bonus names, presence of all bonuses required to be affected by, can't be negated."
},
"animation":{"$ref": "#/definitions/animation"},
"graphics":{
"type": "object",
"additionalProperties" : false,
@ -291,7 +298,7 @@
"additionalProperties" : false,
"required" : ["none", "basic", "advanced", "expert"],
"properties":{
"properties":{
"base":{
"type": "object",
"description": "will be merged with all levels",

View File

@ -101,6 +101,7 @@
"levels" : {
"base":{
"range" : "0",
//no cumulative effect even with mods here
"effects" : {
"bindEffect" : {
"val" : 0,
@ -335,7 +336,7 @@
"base":{
"range" : "0",
"targetModifier":{"smart":true},
"effects" : {
"cumulativeEffects" : {
"primarySkill" : {
"val" : -3,
"type" : "PRIMARY_SKILL",

View File

@ -13,6 +13,7 @@
"base":{
"range" : "0",
"targetModifier":{"smart":true},
//no cumulative effect even with mods here
"effects" : {
"generalDamageReduction" : {
"type" : "GENERAL_DAMAGE_REDUCTION",
@ -43,6 +44,7 @@
"base":{
"range" : "0",
"targetModifier":{"smart":true},
//no cumulative effect even with mods here
"effects" : {
"generalDamageReduction" : {
"type" : "GENERAL_DAMAGE_REDUCTION",
@ -558,7 +560,7 @@
"base":{
"range" : "0",
"targetModifier":{"smart":true},
"effects" : {
"cumulativeEffects" : {
"primarySkill" : {
"type" : "PRIMARY_SKILL",
"subtype" : "primSkill.defence",
@ -569,14 +571,14 @@
}
},
"advanced":{
"effects" : {
"cumulativeEffects" : {
"primarySkill" : {
"val" : -4
}
}
},
"expert":{
"effects" : {
"cumulativeEffects" : {
"primarySkill" : {
"val" : -5
}

View File

@ -130,7 +130,7 @@ void CArtifact::addNewBonus(const std::shared_ptr<Bonus>& b)
void CArtifact::fillWarMachine()
{
switch (id)
switch(id)
{
case ArtifactID::CATAPULT:
warMachine = CreatureID::CATAPULT;
@ -144,8 +144,10 @@ void CArtifact::fillWarMachine()
case ArtifactID::AMMO_CART:
warMachine = CreatureID::AMMO_CART;
break;
default:
warMachine = CreatureID::NONE;
break;
}
warMachine = CreatureID::NONE; //this artifact is not a creature
}
void CGrowingArtifact::levelUpArtifact (CArtifactInstance * art)

View File

@ -100,11 +100,6 @@ namespace SiegeStuffThatShouldBeMovedToHandlers // <=== TODO
using namespace SiegeStuffThatShouldBeMovedToHandlers;
boost::shared_mutex& CCallbackBase::getGsMutex()
{
return *gs->mx;
}
bool CCallbackBase::duringBattle() const
{
return getBattle() != nullptr;

View File

@ -62,7 +62,6 @@ protected:
bool duringBattle() const;
public:
boost::shared_mutex &getGsMutex(); //just return a reference to mutex, does not lock nor anything
boost::optional<PlayerColor> getPlayerID() const;
friend class CBattleInfoEssentials;

View File

@ -45,6 +45,8 @@
*
*/
boost::shared_mutex CGameState::mutex;
template <typename T> class CApplyOnGS;
class CBaseForGSApply
@ -65,7 +67,7 @@ public:
{
T *ptr = static_cast<T*>(pack);
boost::unique_lock<boost::shared_mutex> lock(*gs->mx);
boost::unique_lock<boost::shared_mutex> lock(CGameState::mutex);
ptr->applyGs(gs);
}
};
@ -675,7 +677,6 @@ int CGameState::getDate(Date::EDateType mode) const
CGameState::CGameState()
{
gs = this;
mx = new boost::shared_mutex();
applierGs = new CApplier<CBaseForGSApply>;
registerTypesClientPacks1(*applierGs);
registerTypesClientPacks2(*applierGs);
@ -687,7 +688,6 @@ CGameState::CGameState()
CGameState::~CGameState()
{
//delete mx;//TODO: crash on Linux (mutex must be unlocked before destruction)
map.dellNull();
curB.dellNull();
//delete scenarioOps; //TODO: fix for loading ind delete

View File

@ -213,7 +213,7 @@ public:
CBonusSystemNode globalEffects;
RumorState rumor;
boost::shared_mutex *mx;
static boost::shared_mutex mutex;
void giveHeroArtifact(CGHeroInstance *h, ArtifactID aid);

View File

@ -104,21 +104,6 @@ si32 CStack::magicResistance() const
return magicResistance;
}
void CStack::stackEffectToFeature(std::vector<Bonus> & sf, const Bonus & sse)
{
const CSpell * sp = SpellID(sse.sid).toSpell();
std::vector<Bonus> tmp;
sp->getEffects(tmp, sse.val);
for(Bonus& b : tmp)
{
if(b.turnsRemain == 0)
b.turnsRemain = sse.turnsRemain;
sf.push_back(b);
}
}
bool CStack::willMove(int turn /*= 0*/) const
{
return ( turn ? true : !vstd::contains(state, EBattleStackState::DEFENDING) )

View File

@ -62,7 +62,6 @@ public:
ui32 calculateHealedHealthPoints(ui32 toHeal, const bool resurrect) const;
ui32 level() const;
si32 magicResistance() const override; //include aura of resistance
static void stackEffectToFeature(std::vector<Bonus> & sf, const Bonus & sse);
std::vector<si32> activeSpells() const; //returns vector of active spell IDs sorted by time of cast
const CGHeroInstance *getMyHero() const; //if stack belongs to hero (directly or was by him summoned) returns hero, nullptr otherwise
ui32 totalHealth() const; // total health for all creatures in stack;

View File

@ -1519,11 +1519,19 @@ struct SetStackEffect : public CPackForClient
void applyCl(CClient *cl);
std::vector<ui32> stacks; //affected stacks (IDs)
//regular effects
std::vector<Bonus> effect; //bonuses to apply
std::vector<std::pair<ui32, Bonus> > uniqueBonuses; //bonuses per single stack
//cumulative effects
std::vector<Bonus> cumulativeEffects; //bonuses to apply
std::vector<std::pair<ui32, Bonus> > cumulativeUniqueBonuses; //bonuses per single stack
template <typename Handler> void serialize(Handler &h, const int version)
{
h & stacks & effect & uniqueBonuses;
h & cumulativeEffects & cumulativeUniqueBonuses;
}
};

View File

@ -1554,33 +1554,39 @@ void actualizeEffect(CStack * s, const std::vector<Bonus> & ef)
DLL_LINKAGE void SetStackEffect::applyGs(CGameState *gs)
{
if(effect.empty())
if(effect.empty() && cumulativeEffects.empty())
{
logGlobal->errorStream() << "Trying to apply SetStackEffect with no effects";
return;
}
int spellid = effect.begin()->sid; //effects' source ID
si32 spellid = effect.empty() ? cumulativeEffects.begin()->sid : effect.begin()->sid; //effects' source ID
auto processEffect = [spellid, this](CStack * sta, const Bonus & effect)
auto processEffect = [spellid, this](CStack * sta, const Bonus & effect, bool cumulative)
{
if(!sta->hasBonus(Selector::source(Bonus::SPELL_EFFECT, spellid).And(Selector::typeSubtype(effect.type, effect.subtype)))
|| spellid == SpellID::DISRUPTING_RAY || spellid == SpellID::ACID_BREATH_DEFENSE)
if(cumulative || !sta->hasBonus(Selector::source(Bonus::SPELL_EFFECT, spellid).And(Selector::typeSubtype(effect.type, effect.subtype))))
{
//no such effect or cumulative - add new
logBonus->traceStream() << sta->nodeName() << " receives a new bonus: " << effect.Description();
sta->addNewBonus(std::make_shared<Bonus>(effect));
}
else
{
logBonus->traceStream() << sta->nodeName() << " updated bonus: " << effect.Description();
actualizeEffect(sta, effect);
}
};
for(ui32 id : stacks)
{
CStack *s = gs->curB->getStack(id);
if(s)
{
for(const Bonus & fromEffect : effect)
processEffect(s, fromEffect);
processEffect(s, fromEffect, false);
for(const Bonus & fromEffect : cumulativeEffects)
processEffect(s, fromEffect, true);
}
else
logNetwork->errorStream() << "Cannot find stack " << id;
}
@ -1589,7 +1595,16 @@ DLL_LINKAGE void SetStackEffect::applyGs(CGameState *gs)
{
CStack *s = gs->curB->getStack(para.first);
if(s)
processEffect(s, para.second);
processEffect(s, para.second, false);
else
logNetwork->errorStream() << "Cannot find stack " << para.first;
}
for(auto & para : cumulativeUniqueBonuses)
{
CStack *s = gs->curB->getStack(para.first);
if(s)
processEffect(s, para.second, true);
else
logNetwork->errorStream() << "Cannot find stack " << para.first;
}

View File

@ -98,7 +98,7 @@
<AdditionalOptions>/MP4 %(AdditionalOptions) /bigobj
/Zm150</AdditionalOptions>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>$(BOOSTDIR); $(ZLIBDIR);$(SDLDIR)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(BOOSTDIR);$(ZLIBDIR);$(SDLDIR)</AdditionalIncludeDirectories>
<MinimalRebuild>false</MinimalRebuild>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
@ -139,7 +139,7 @@
<PreprocessorDefinitions>VCMI_DLL;VCMI_NO_EXTRA_VERSION;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PrecompiledHeaderFile>StdInc.h</PrecompiledHeaderFile>
<PrecompiledHeader>Use</PrecompiledHeader>
<AdditionalIncludeDirectories>$(BOOSTDIR); $(ZLIBDIR);$(SDLDIR)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(BOOSTDIR);$(ZLIBDIR);$(SDLDIR)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalDependencies>minizip.lib;zlib.lib;%(AdditionalDependencies)</AdditionalDependencies>

View File

@ -7,7 +7,7 @@
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "CZipSaver.h"
@ -17,10 +17,10 @@ CZipOutputStream::CZipOutputStream(CZipSaver * owner_, zipFile archive, const st
owner(owner_)
{
zip_fileinfo fileInfo;
std::time_t t = time(nullptr);
fileInfo.dosDate = 0;
fileInfo.dosDate = 0;
struct tm * localTime = std::localtime(&t);
fileInfo.tmz_date.tm_hour = localTime->tm_hour;
fileInfo.tmz_date.tm_mday = localTime->tm_mday;
@ -28,10 +28,10 @@ CZipOutputStream::CZipOutputStream(CZipSaver * owner_, zipFile archive, const st
fileInfo.tmz_date.tm_mon = localTime->tm_mon;
fileInfo.tmz_date.tm_sec = localTime->tm_sec;
fileInfo.tmz_date.tm_year = localTime->tm_year;
fileInfo.external_fa = 0; //???
fileInfo.internal_fa = 0;
fileInfo.external_fa = 0; //???
fileInfo.internal_fa = 0;
int status = zipOpenNewFileInZip4_64(
handle,
archiveFilename.c_str(),
@ -53,10 +53,10 @@ CZipOutputStream::CZipOutputStream(CZipSaver * owner_, zipFile archive, const st
0,//flagBase
0//zip64
);
if(status != ZIP_OK)
throw new std::runtime_error("CZipOutputStream: zipOpenNewFileInZip failed");
owner->activeStream = this;
}
@ -71,7 +71,7 @@ CZipOutputStream::~CZipOutputStream()
si64 CZipOutputStream::write(const ui8 * data, si64 size)
{
int ret = zipWriteInFileInZip(handle, (const void*)data, (unsigned)size);
if (ret == ZIP_OK)
return size;
else
@ -79,41 +79,41 @@ si64 CZipOutputStream::write(const ui8 * data, si64 size)
}
///CZipSaver
CZipSaver::CZipSaver(std::shared_ptr<CIOApi> api, const std::string & path):
CZipSaver::CZipSaver(std::shared_ptr<CIOApi> api, const boost::filesystem::path & path):
ioApi(api),
zipApi(ioApi->getApiStructure()),
handle(nullptr),
activeStream(nullptr)
{
handle = zipOpen2_64(path.c_str(), APPEND_STATUS_CREATE, nullptr, &zipApi);
handle = zipOpen2_64((const void *) & path, APPEND_STATUS_CREATE, nullptr, &zipApi);
if (handle == nullptr)
throw new std::runtime_error("CZipSaver: Failed to create archive");
throw new std::runtime_error("CZipSaver: Failed to create archive");
}
CZipSaver::~CZipSaver()
{
if(activeStream != nullptr)
{
logGlobal->error("CZipSaver::~CZipSaver: active stream found");
logGlobal->error("CZipSaver::~CZipSaver: active stream found");
zipCloseFileInZip(handle);
}
if(handle != nullptr)
{
int status = zipClose(handle, nullptr);
if (status != ZIP_OK)
logGlobal->errorStream() << "CZipSaver: archive finalize failed: "<<status;
logGlobal->errorStream() << "CZipSaver: archive finalize failed: "<<status;
}
}
std::unique_ptr<COutputStream> CZipSaver::addFile(const std::string & archiveFilename)
{
if(activeStream != nullptr)
throw new std::runtime_error("CZipSaver::addFile: stream already opened");
std::unique_ptr<COutputStream> stream(new CZipOutputStream(this, handle, archiveFilename));
return std::move(stream);
}

View File

@ -23,16 +23,16 @@ public:
* @brief constructs zip stream from already opened file
* @param archive archive handle, must be opened
* @param archiveFilename name of file to write
*/
*/
explicit CZipOutputStream(CZipSaver * owner_, zipFile archive, const std::string & archiveFilename);
~CZipOutputStream();
si64 write(const ui8 * data, si64 size) override;
si64 seek(si64 position) override {return -1;};
si64 tell() override {return 0;};
si64 skip(si64 delta) override {return 0;};
si64 getSize() override {return 0;};
si64 getSize() override {return 0;};
private:
zipFile handle;
CZipSaver * owner;
@ -40,17 +40,17 @@ private:
class DLL_LINKAGE CZipSaver
{
public:
explicit CZipSaver(std::shared_ptr<CIOApi> api, const std::string & path);
public:
explicit CZipSaver(std::shared_ptr<CIOApi> api, const boost::filesystem::path & path);
virtual ~CZipSaver();
std::unique_ptr<COutputStream> addFile(const std::string & archiveFilename);
private:
std::shared_ptr<CIOApi> ioApi;
zlib_filefunc64_def zipApi;
zlib_filefunc64_def zipApi;
zipFile handle;
///due to minizip design only one file stream may opened at a time
COutputStream * activeStream;
friend class CZipOutputStream;

View File

@ -204,7 +204,7 @@ zlib_filefunc64_def CProxyROIOApi::getApiStructure()
CInputStream * CProxyROIOApi::openFile(const boost::filesystem::path& filename, int mode)
{
logGlobal->traceStream() << "CProxyIOApi: stream opened for " <<filename.string() <<" with mode "<<mode;
logGlobal->traceStream() << "CProxyROIOApi: stream opened for " <<filename.string() <<" with mode "<<mode;
data->seek(0);
return data;

View File

@ -14,7 +14,7 @@
#include "../ConstTransitivePtr.h"
#include "../GameConstants.h"
const ui32 SERIALIZATION_VERSION = 771;
const ui32 SERIALIZATION_VERSION = 773;
const ui32 MINIMAL_SERIALIZATION_VERSION = 753;
const std::string SAVEGAME_MAGIC = "VCMISVG";

View File

@ -79,11 +79,12 @@ ESpellCastResult AdventureSpellMechanics::applyAdventureEffects(const SpellCastE
{
if(owner->hasEffects())
{
//todo: cumulative effects support
const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);
std::vector<Bonus> bonuses;
owner->getEffects(bonuses, schoolLevel);
owner->getEffects(bonuses, schoolLevel, false, parameters.caster->getEnchantPower(owner));
for(Bonus b : bonuses)
{

View File

@ -445,120 +445,133 @@ void DefaultSpellMechanics::battleLogDefault(std::vector<MetaString> & logLines,
void DefaultSpellMechanics::applyBattleEffects(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
{
//applying effects
if(owner->isOffensiveSpell())
{
const int rawDamage = parameters.getEffectValue();
int chainLightningModifier = 0;
for(auto & attackedCre : ctx.attackedCres)
{
BattleStackAttacked bsa;
bsa.damageAmount = owner->adjustRawDamage(parameters.caster, attackedCre, rawDamage) >> chainLightningModifier;
ctx.addDamageToDisplay(bsa.damageAmount);
bsa.stackAttacked = (attackedCre)->ID;
if(parameters.mode == ECastingMode::ENCHANTER_CASTING) //multiple damage spells cast
bsa.attackerID = parameters.casterStack->ID;
else
bsa.attackerID = -1;
(attackedCre)->prepareAttacked(bsa, env->getRandomGenerator());
ctx.si.stacks.push_back(bsa);
if(owner->id == SpellID::CHAIN_LIGHTNING)
++chainLightningModifier;
}
}
defaultDamageEffect(env, parameters, ctx);
if(owner->hasEffects())
defaultTimedEffect(env, parameters, ctx);
}
void DefaultSpellMechanics::defaultDamageEffect(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
{
const int rawDamage = parameters.getEffectValue();
int chainLightningModifier = 0;
for(auto & attackedCre : ctx.attackedCres)
{
SetStackEffect sse;
//get default spell duration (spell power with bonuses for heroes)
int duration = parameters.enchantPower;
//generate actual stack bonuses
{
int maxDuration = 0;
std::vector<Bonus> tmp;
owner->getEffects(tmp, parameters.effectLevel);
for(Bonus& b : tmp)
{
//use configured duration if present
if(b.turnsRemain == 0)
b.turnsRemain = duration;
vstd::amax(maxDuration, b.turnsRemain);
sse.effect.push_back(b);
}
//if all spell effects have special duration, use it
duration = maxDuration;
}
//fix to original config: shield should display damage reduction
if(owner->id == SpellID::SHIELD || owner->id == SpellID::AIR_SHIELD)
{
sse.effect.back().val = (100 - sse.effect.back().val);
}
//we need to know who cast Bind
if(owner->id == SpellID::BIND && parameters.casterStack)
{
sse.effect.back().additionalInfo = parameters.casterStack->ID;
}
std::shared_ptr<Bonus> bonus = nullptr;
if(parameters.casterHero)
bonus = parameters.casterHero->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, owner->id));
//TODO does hero specialty should affects his stack casting spells?
BattleStackAttacked bsa;
bsa.damageAmount = owner->adjustRawDamage(parameters.caster, attackedCre, rawDamage) >> chainLightningModifier;
ctx.addDamageToDisplay(bsa.damageAmount);
si32 power = 0;
for(const CStack * affected : ctx.attackedCres)
{
sse.stacks.push_back(affected->ID);
bsa.stackAttacked = (attackedCre)->ID;
if(parameters.mode == ECastingMode::ENCHANTER_CASTING) //multiple damage spells cast
bsa.attackerID = parameters.casterStack->ID;
else
bsa.attackerID = -1;
(attackedCre)->prepareAttacked(bsa, env->getRandomGenerator());
ctx.si.stacks.push_back(bsa);
//Apply hero specials - peculiar enchants
const ui8 tier = std::max((ui8)1, affected->getCreature()->level); //don't divide by 0 for certain creatures (commanders, war machines)
if(bonus)
{
switch(bonus->additionalInfo)
{
case 0: //normal
{
switch(tier)
{
case 1: case 2:
power = 3;
break;
case 3: case 4:
power = 2;
break;
case 5: case 6:
power = 1;
break;
}
Bonus specialBonus(sse.effect.back());
specialBonus.val = power; //it doesn't necessarily make sense for some spells, use it wisely
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional premy to given effect
}
break;
case 1: //only Coronius as yet
{
power = std::max(5 - tier, 0);
Bonus specialBonus(Bonus::N_TURNS, Bonus::PRIMARY_SKILL, Bonus::SPELL_EFFECT, power, owner->id, PrimarySkill::ATTACK);
specialBonus.turnsRemain = duration;
sse.uniqueBonuses.push_back(std::pair<ui32,Bonus> (affected->ID, specialBonus)); //additional attack to Slayer effect
}
break;
}
}
if (parameters.casterHero && parameters.casterHero->hasBonusOfType(Bonus::SPECIAL_BLESS_DAMAGE, owner->id)) //TODO: better handling of bonus percentages
{
int damagePercent = parameters.casterHero->level * parameters.casterHero->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, owner->id.toEnum()) / tier;
Bonus specialBonus(Bonus::N_TURNS, Bonus::CREATURE_DAMAGE, Bonus::SPELL_EFFECT, damagePercent, owner->id, 0, Bonus::PERCENT_TO_ALL);
specialBonus.turnsRemain = duration;
sse.uniqueBonuses.push_back (std::pair<ui32,Bonus> (affected->ID, specialBonus));
}
}
if(!sse.stacks.empty())
env->sendAndApply(&sse);
if(owner->id == SpellID::CHAIN_LIGHTNING)
++chainLightningModifier;
}
}
void DefaultSpellMechanics::defaultTimedEffect(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const
{
SetStackEffect sse;
//get default spell duration (spell power with bonuses for heroes)
int duration = parameters.enchantPower;
//generate actual stack bonuses
{
si32 maxDuration = 0;
owner->getEffects(sse.effect, parameters.effectLevel, false, duration, &maxDuration);
owner->getEffects(sse.cumulativeEffects, parameters.effectLevel, true, duration, &maxDuration);
//if all spell effects have special duration, use it later for special bonuses
duration = maxDuration;
}
//fix to original config: shield should display damage reduction
if(owner->id == SpellID::SHIELD || owner->id == SpellID::AIR_SHIELD)
{
sse.effect.at(sse.effect.size() - 1).val = (100 - sse.effect.back().val);
}
//we need to know who cast Bind
else if(owner->id == SpellID::BIND && parameters.casterStack)
{
sse.effect.at(sse.effect.size() - 1).additionalInfo = parameters.casterStack->ID;
}
std::shared_ptr<Bonus> bonus = nullptr;
if(parameters.casterHero)
bonus = parameters.casterHero->getBonusLocalFirst(Selector::typeSubtype(Bonus::SPECIAL_PECULIAR_ENCHANT, owner->id));
//TODO does hero specialty should affects his stack casting spells?
for(const CStack * affected : ctx.attackedCres)
{
si32 power = 0;
sse.stacks.push_back(affected->ID);
//Apply hero specials - peculiar enchants
const ui8 tier = std::max((ui8)1, affected->getCreature()->level); //don't divide by 0 for certain creatures (commanders, war machines)
if(bonus)
{
switch(bonus->additionalInfo)
{
case 0: //normal
{
switch(tier)
{
case 1:
case 2:
power = 3;
break;
case 3:
case 4:
power = 2;
break;
case 5:
case 6:
power = 1;
break;
}
for(const Bonus & b : sse.effect)
{
Bonus specialBonus(b);
specialBonus.val = power; //it doesn't necessarily make sense for some spells, use it wisely
specialBonus.turnsRemain = duration;
sse.uniqueBonuses.push_back(std::pair<ui32, Bonus>(affected->ID, specialBonus)); //additional premy to given effect
}
for(const Bonus & b : sse.cumulativeEffects)
{
Bonus specialBonus(b);
specialBonus.val = power; //it doesn't necessarily make sense for some spells, use it wisely
specialBonus.turnsRemain = duration;
sse.cumulativeUniqueBonuses.push_back(std::pair<ui32, Bonus>(affected->ID, specialBonus)); //additional premy to given effect
}
}
break;
case 1: //only Coronius as yet
{
power = std::max(5 - tier, 0);
Bonus specialBonus(Bonus::N_TURNS, Bonus::PRIMARY_SKILL, Bonus::SPELL_EFFECT, power, owner->id, PrimarySkill::ATTACK);
specialBonus.turnsRemain = duration;
sse.uniqueBonuses.push_back(std::pair<ui32,Bonus>(affected->ID, specialBonus)); //additional attack to Slayer effect
}
break;
}
}
if(parameters.casterHero && parameters.casterHero->hasBonusOfType(Bonus::SPECIAL_BLESS_DAMAGE, owner->id)) //TODO: better handling of bonus percentages
{
int damagePercent = parameters.casterHero->level * parameters.casterHero->valOfBonuses(Bonus::SPECIAL_BLESS_DAMAGE, owner->id.toEnum()) / tier;
Bonus specialBonus(Bonus::N_TURNS, Bonus::CREATURE_DAMAGE, Bonus::SPELL_EFFECT, damagePercent, owner->id, 0, Bonus::PERCENT_TO_ALL);
specialBonus.turnsRemain = duration;
sse.uniqueBonuses.push_back(std::pair<ui32,Bonus>(affected->ID, specialBonus));
}
}
if(!sse.stacks.empty())
env->sendAndApply(&sse);
}
std::vector<BattleHex> DefaultSpellMechanics::rangeInHexes(BattleHex centralHex, ui8 schoolLvl, ui8 side, bool *outDroppedHexes) const
{
using namespace SRSLPraserHelpers;

View File

@ -75,6 +75,9 @@ protected:
protected:
void doDispell(BattleInfo * battle, const BattleSpellCast * packet, const CSelector & selector) const;
bool canDispell(const IBonusBearer * obj, const CSelector & selector, const std::string &cachingStr = "") const;
void defaultDamageEffect(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const;
void defaultTimedEffect(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, SpellCastContext & ctx) const;
private:
void cast(const SpellCastEnvironment * env, const BattleSpellCastParameters & parameters, std::vector <const CStack*> & reflected) const;

View File

@ -350,7 +350,7 @@ bool CSpell::isSpecialSpell() const
bool CSpell::hasEffects() const
{
return !levels[0].effects.empty();
return !levels[0].effects.empty() || !levels[0].cumulativeEffects.empty();
}
const std::string & CSpell::getIconImmune() const
@ -382,7 +382,7 @@ si32 CSpell::getProbability(const TFaction factionId) const
return probabilities.at(factionId);
}
void CSpell::getEffects(std::vector<Bonus> & lst, const int level) const
void CSpell::getEffects(std::vector<Bonus> & lst, const int level, const bool cumulative, const si32 duration, boost::optional<si32 *> maxDuration/* = boost::none*/) const
{
if(level < 0 || level >= GameConstants::SPELL_SCHOOL_LEVELS)
{
@ -390,19 +390,29 @@ void CSpell::getEffects(std::vector<Bonus> & lst, const int level) const
return;
}
const std::vector<Bonus> & effects = levels[level].effects;
const auto & levelObject = levels.at(level);
if(effects.empty())
if(levelObject.effects.empty() && levelObject.cumulativeEffects.empty())
{
logGlobal->errorStream() << __FUNCTION__ << " This spell (" + name + ") has no effects for level " << level;
return;
}
const auto & effects = cumulative ? levelObject.cumulativeEffects : levelObject.effects;
lst.reserve(lst.size() + effects.size());
for(const Bonus & b : effects)
for(const auto b : effects)
{
lst.push_back(Bonus(b));
Bonus nb(*b);
//use configured duration if present
if(nb.turnsRemain == 0)
nb.turnsRemain = duration;
if(maxDuration)
vstd::amax(*(maxDuration.get()), nb.turnsRemain);
lst.push_back(nb);
}
}
@ -1029,9 +1039,25 @@ CSpell * CSpellHandler::loadFromJson(const JsonNode & json, const std::string &
if(usePowerAsValue)
b->val = levelPower;
levelObject.effectsTmp.push_back(b);
levelObject.effects.push_back(b);
}
for(const auto & elem : levelNode["cumulativeEffects"].Struct())
{
const JsonNode & bonusNode = elem.second;
auto b = JsonUtils::parseBonus(bonusNode);
const bool usePowerAsValue = bonusNode["val"].isNull();
//TODO: make this work. see CSpellHandler::afterLoadFinalization()
//b->sid = spell->id; //for all
b->source = Bonus::SPELL_EFFECT;//for all
if(usePowerAsValue)
b->val = levelPower;
levelObject.cumulativeEffects.push_back(b);
}
}
return spell;
@ -1044,14 +1070,10 @@ void CSpellHandler::afterLoadFinalization()
{
for(auto & level: spell->levels)
{
for(auto bonus : level.effectsTmp)
{
level.effects.push_back(*bonus);
}
level.effectsTmp.clear();
for(auto & bonus: level.effects)
bonus.sid = spell->id;
bonus->sid = spell->id;
for(auto & bonus: level.cumulativeEffects)
bonus->sid = spell->id;
}
spell->setup();
}

View File

@ -130,16 +130,35 @@ public:
bool clearAffected;
std::string range;
std::vector<Bonus> effects;
std::vector<std::shared_ptr<Bonus>> effectsTmp; //TODO: this should replace effects
std::vector<std::shared_ptr<Bonus>> effects;
std::vector<std::shared_ptr<Bonus>> cumulativeEffects;
LevelInfo();
~LevelInfo();
template <typename Handler> void serialize(Handler &h, const int version)
{
h & description & cost & power & AIValue & smartTarget & range & effects;
h & description & cost & power & AIValue & smartTarget & range;
if(version >= 773)
{
h & effects & cumulativeEffects;
}
else
{
//all old effects treated as not cumulative, special cases handled by CSpell::serialize
std::vector<Bonus> old;
h & old;
if(!h.saving)
{
effects.clear();
cumulativeEffects.clear();
for(const Bonus & oldBonus : old)
effects.push_back(std::make_shared<Bonus>(oldBonus));
}
}
h & clearTarget & clearAffected;
}
};
@ -180,7 +199,7 @@ public:
si32 level;
std::map<ESpellSchool, bool> school; //todo: use this instead of separate boolean fields
std::map<ESpellSchool, bool> school;
si32 power; //spell's power
@ -215,8 +234,7 @@ public:
bool isSpecialSpell() const;
bool hasEffects() const;
void getEffects(std::vector<Bonus> &lst, const int level) const;
void getEffects(std::vector<Bonus> & lst, const int level, const bool cumulative, const si32 duration, boost::optional<si32 *> maxDuration = boost::none) const;
///calculate spell damage on stack taking caster`s secondary skills and affectedCreature`s bonuses into account
ui32 calculateDamage(const ISpellCaster * caster, const CStack * affectedCreature, int spellSchoolLevel, int usedSpellPower) const;
@ -264,6 +282,12 @@ public:
if(!h.saving)
setup();
//backward compatibility
//can not be added to level structure as level structure does not know spell id
if(!h.saving && version < 773)
if(id == SpellID::DISRUPTING_RAY || id == SpellID::ACID_BREATH_DEFENSE)
for(auto & level : levels)
std::swap(level.effects, level.cumulativeEffects);
}
friend class CSpellHandler;
friend class Graphics;

View File

@ -4489,30 +4489,32 @@ bool CGameHandler::makeCustomAction(BattleAction &ba)
}
void CGameHandler::stackAppearTrigger(const CStack *st)
void CGameHandler::stackEnchantedTrigger(const CStack * st)
{
auto bl = *(st->getBonuses(Selector::type(Bonus::ENCHANTED)));
for (auto b : bl)
for(auto b : bl)
{
SetStackEffect sse;
int val = bl.valOfBonuses(Selector::typeSubtype(b->type, b->subtype));
if (val > 3)
if(val > 3)
{
for (auto s : gs->curB->battleGetAllStacks())
for(auto s : gs->curB->battleGetAllStacks())
{
if (battleMatchOwner(st, s, true) && s->isValidTarget()) //all allied
if(battleMatchOwner(st, s, true) && s->isValidTarget()) //all allied
sse.stacks.push_back (s->ID);
}
}
else
sse.stacks.push_back (st->ID);
Bonus pseudoBonus;
pseudoBonus.sid = b->subtype;
pseudoBonus.val = ((val > 3) ? (val - 3) : val);
pseudoBonus.turnsRemain = 50;
st->stackEffectToFeature(sse.effect, pseudoBonus);
if (sse.effect.size())
const CSpell * sp = SpellID(b->subtype).toSpell();
const int level = ((val > 3) ? (val - 3) : val);
sp->getEffects(sse.effect, level, false, 50);
//this makes effect accumulate for at most 50 turns by default, but effect may be permanent and last till the end of battle
sp->getEffects(sse.cumulativeEffects, level, true, 50);
if(!sse.effect.empty() || !sse.cumulativeEffects.empty())
sendAndApply(&sse);
}
}
@ -4526,7 +4528,6 @@ void CGameHandler::stackTurnTrigger(const CStack *st)
bte.additionalInfo = 0;
if (st->alive())
{
stackAppearTrigger(st);
//unbind
if (st->hasBonus(Selector::type(Bonus::BIND_EFFECT)))
{
@ -5724,7 +5725,7 @@ void CGameHandler::runBattle()
}
}
stackAppearTrigger(stack);
stackEnchantedTrigger(stack);
}
//spells opening battle
@ -5749,11 +5750,14 @@ void CGameHandler::runBattle()
}
}
bool firstRound = true;//FIXME: why first round is -1?
//main loop
while (!battleResult.get()) //till the end of the battle ;]
{
BattleNextRound bnr;
bnr.round = gs->curB->round + 1;
logGlobal->debug("Round %d", bnr.round);
sendAndApply(&bnr);
auto obstacles = gs->curB->obstacles; //we copy container, because we're going to modify it
@ -5766,6 +5770,12 @@ void CGameHandler::runBattle()
const BattleInfo & curB = *gs->curB;
for(auto stack : curB.stacks)
{
if(stack->alive() && !firstRound)
stackEnchantedTrigger(stack);
}
//stack loop
const CStack *next;
@ -5994,6 +6004,7 @@ void CGameHandler::runBattle()
}
}
firstRound = false;
}
endBattle(gs->curB->tile, gs->curB->battleGetFightingHero(0), gs->curB->battleGetFightingHero(1));
@ -6207,6 +6218,13 @@ void CGameHandler::handleCheatCode(std::string & cheat, PlayerColor player, cons
smp.hid = hero->id;
smp.val = 1000000;
sendAndApply(&smp);
GiveBonus gb(GiveBonus::HERO);
gb.bonus.type = Bonus::FREE_SHIP_BOARDING;
gb.bonus.duration = Bonus::ONE_DAY;
gb.bonus.source = Bonus::OTHER;
gb.id = hero->id.getNum();
giveHeroBonus(&gb);
}
else if (cheat == "vcmiformenos")
{

View File

@ -196,7 +196,7 @@ public:
bool makeBattleAction(BattleAction &ba);
bool makeAutomaticAction(const CStack *stack, BattleAction &ba); //used when action is taken by stack without volition of player (eg. unguided catapult attack)
bool makeCustomAction(BattleAction &ba);
void stackAppearTrigger(const CStack *stack);
void stackEnchantedTrigger(const CStack * stack);
void stackTurnTrigger(const CStack *stack);
void handleDamageFromObstacle(const CObstacleInstance &obstacle, const CStack * curStack); //checks if obstacle is land mine and handles possible consequences
void removeObstacle(const CObstacleInstance &obstacle);

View File

@ -21,7 +21,9 @@
#include "../lib/CRandomGenerator.h"
#include "../lib/VCMI_Lib.h"
BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain_Type)
BOOST_AUTO_TEST_SUITE(CMapEditManager_Suite)
BOOST_AUTO_TEST_CASE(DrawTerrain_Type)
{
logGlobal->info("CMapEditManager_DrawTerrain_Type start");
try
@ -110,7 +112,7 @@ BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain_Type)
logGlobal->info("CMapEditManager_DrawTerrain_Type finish");
}
BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain_View)
BOOST_AUTO_TEST_CASE(DrawTerrain_View)
{
logGlobal->info("CMapEditManager_DrawTerrain_View start");
try
@ -178,3 +180,5 @@ BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain_View)
}
logGlobal->info("CMapEditManager_DrawTerrain_View finish");
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -26,7 +26,9 @@
static const int TEST_RANDOM_SEED = 1337;
BOOST_AUTO_TEST_CASE(MapFormat_Random)
BOOST_AUTO_TEST_SUITE(MapFormat_Suite)
BOOST_AUTO_TEST_CASE(Random)
{
logGlobal->info("MapFormat_Random start");
BOOST_TEST_CHECKPOINT("MapFormat_Random start");
@ -115,7 +117,7 @@ static void addToArchive(CZipSaver & saver, const JsonNode & data, const std::st
}
}
BOOST_AUTO_TEST_CASE(MapFormat_Objects)
BOOST_AUTO_TEST_CASE(Objects)
{
logGlobal->info("MapFormat_Objects start");
@ -202,3 +204,5 @@ BOOST_AUTO_TEST_CASE(MapFormat_Objects)
logGlobal->info("MapFormat_Objects finish");
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -9,44 +9,45 @@
*
*/
#include "StdInc.h"
#include <boost/test/unit_test.hpp>
#include "../lib/filesystem/CMemoryBuffer.h"
struct CMemoryBufferFixture
{
CMemoryBuffer subject;
};
BOOST_FIXTURE_TEST_CASE(CMemoryBuffer_Empty, CMemoryBufferFixture)
BOOST_AUTO_TEST_SUITE(CMemoryBuffer_Suite)
BOOST_FIXTURE_TEST_CASE(empty, CMemoryBufferFixture)
{
BOOST_REQUIRE_EQUAL(0, subject.tell());
BOOST_REQUIRE_EQUAL(0, subject.getSize());
si32 dummy = 1337;
auto ret = subject.read((ui8 *)&dummy, sizeof(si32));
BOOST_CHECK_EQUAL(0, ret);
BOOST_CHECK_EQUAL(0, ret);
BOOST_CHECK_EQUAL(1337, dummy);
BOOST_CHECK_EQUAL(0, subject.tell());
}
BOOST_FIXTURE_TEST_CASE(CMemoryBuffer_Write, CMemoryBufferFixture)
BOOST_FIXTURE_TEST_CASE(write, CMemoryBufferFixture)
{
const si32 initial = 1337;
subject.write((const ui8 *)&initial, sizeof(si32));
BOOST_CHECK_EQUAL(4, subject.tell());
subject.seek(0);
BOOST_CHECK_EQUAL(0, subject.tell());
si32 current = 0;
auto ret = subject.read((ui8 *)&current, sizeof(si32));
BOOST_CHECK_EQUAL(sizeof(si32), ret);
BOOST_CHECK_EQUAL(sizeof(si32), ret);
BOOST_CHECK_EQUAL(initial, current);
BOOST_CHECK_EQUAL(4, subject.tell());
BOOST_CHECK_EQUAL(4, subject.tell());
}
BOOST_AUTO_TEST_SUITE_END()