diff --git a/client/CBattleInterface.cpp b/client/CBattleInterface.cpp index c287ac928..91d32e08b 100644 --- a/client/CBattleInterface.cpp +++ b/client/CBattleInterface.cpp @@ -1184,7 +1184,7 @@ void CBattleInterface::addNewAnim(CBattleAnimation * anim) CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSet * army2, CGHeroInstance *hero1, CGHeroInstance *hero2, const SDL_Rect & myRect, CPlayerInterface * att, CPlayerInterface * defen) : queue(NULL), attackingHeroInstance(hero1), defendingHeroInstance(hero2), animCount(0), - activeStack(NULL), stackToActivate(NULL), mouseHoveredStack(-1), lastMouseHoveredStackAnimationTime(-1), previouslyHoveredHex(-1), + activeStack(NULL), stackToActivate(NULL), mouseHoveredStack(-1), lastMouseHoveredStackAnimationTime(-1), previouslyHoveredHex(-1), spellSelMode(NO_LOCATION), currentlyHoveredHex(-1), attackingHex(-1), tacticianInterface(NULL), stackCanCastSpell(false), spellDestSelectMode(false), spellToCast(NULL), siegeH(NULL), attackerInt(att), defenderInt(defen), curInt(att), animIDhelper(0), givenCommand(NULL), myTurn(false), resWindow(NULL), moveStarted(false), moveSh(-1), bresult(NULL) @@ -2003,7 +2003,7 @@ void CBattleInterface::keyPressed(const SDL_KeyboardEvent & key) } void CBattleInterface::mouseMoved(const SDL_MouseMotionEvent &sEvent) { - if(activeStack!= NULL && !spellDestSelectMode) + if(activeStack && !spellDestSelectMode) { int lastMouseHoveredStack = mouseHoveredStack; mouseHoveredStack = -1; @@ -2039,6 +2039,18 @@ void CBattleInterface::mouseMoved(const SDL_MouseMotionEvent &sEvent) //display the possibility to heal this creature CCS->curh->changeGraphic(1,17); } + else if (sactive->hasBonusOfType(Bonus::DAEMON_SUMMONING)) + { + CCS->curh->changeGraphic(3, 0); + } + else if (stackCanCastSpell && spellSelMode > STACK_SPELL_CANCELLED) //player did not decide to cancel this spell + { + //spellDestSelectMode + if (curInt->cb->battleCanCastThisSpell(creatureSpellToCast, THex(myNumber)) == SpellCasting::OK) + CCS->curh->changeGraphic(3, 0); + //if (battleIsImmune(NULL, spellToCast, SpellCasting::CREATURE_ACTIVE_CASTING, myNumber) == SpellCasting::OK) + //if (battleCanCastThisSpell(curInt->playerID, spellToCast, SpellCasting::CREATURE_ACTIVE_CASTING)) + } else { //info about creature @@ -2709,11 +2721,13 @@ void CBattleInterface::giveCommand(ui8 action, THex tile, ui32 stack, si32 addit //some basic validations switch(action) { - case 6: - assert(curInt->cb->battleGetStackByPos(additional)); //stack to attack must exist - case 2: case 7: case 9: - assert(tile < BFIELD_SIZE); - break; + case BattleAction::WALK_AND_ATTACK: + assert(curInt->cb->battleGetStackByPos(additional)); //stack to attack must exist + case BattleAction::WALK: + case BattleAction::SHOOT: + case BattleAction::CATAPULT: + assert(tile < BFIELD_SIZE); + break; } if(!tacticsMode) @@ -2784,7 +2798,7 @@ const CGHeroInstance * CBattleInterface::getActiveHero() void CBattleInterface::hexLclicked(int whichOne) { const CStack * actSt = activeStack; - const CStack* dest = curInt->cb->battleGetStackByPos(whichOne); //creature at destination tile; -1 if there is no one + const CStack* dest = curInt->cb->battleGetStackByPos(whichOne, false); //creature at destination tile; -1 if there is no one if(!actSt) { tlog3 << "Hex l-clicked when no active stack!\n"; @@ -2797,29 +2811,30 @@ void CBattleInterface::hexLclicked(int whichOne) { if(!myTurn) return; //we are not permit to do anything - if(spellDestSelectMode) + if(spellDestSelectMode) //TODO: choose target for area creature spell { //checking destination bool allowCasting = true; bool onlyAlive = spellToCast->additionalInfo != 38 && spellToCast->additionalInfo != 39; //when casting resurrection or animate dead we should be allow to select dead stack + //TODO: more general handling of dead targets switch(spellSelMode) { - case 1: - if(!curInt->cb->battleGetStackByPos(whichOne, onlyAlive) || curInt->playerID != curInt->cb->battleGetStackByPos(whichOne, onlyAlive)->owner ) + case FRIENDLY_CREATURE: + if(!curInt->cb->battleGetStackByPos(whichOne, onlyAlive) || curInt->playerID != dest->owner ) allowCasting = false; break; - case 2: - if(!curInt->cb->battleGetStackByPos(whichOne, onlyAlive) || curInt->playerID == curInt->cb->battleGetStackByPos(whichOne, onlyAlive)->owner ) + case HOSTILE_CREATURE: + if(!curInt->cb->battleGetStackByPos(whichOne, onlyAlive) || curInt->playerID == dest->owner ) allowCasting = false; break; - case 3: + case ANY_CREATURE: if(!curInt->cb->battleGetStackByPos(whichOne, onlyAlive)) allowCasting = false; break; - case 4: + case OBSTACLE: if(!blockedByObstacle(whichOne)) allowCasting = false; - case 5: //teleport + case TELEPORT: //teleport const CSpell *s = CGI->spellh->spells[spellToCast->additionalInfo]; ui8 skill = getActiveHero()->getSpellSchoolLevel(s); //skill level if (!curInt->cb->battleCanTeleportTo(activeStack, whichOne, skill)) @@ -2836,11 +2851,198 @@ void CBattleInterface::hexLclicked(int whichOne) endCastingSpell(); } } - else //we don't cast any spell + else //we don't aim for spell target { - if(!dest || !dest->alive()) //no creature at that tile + bool walkableTile = false; + if (dest) { - if(std::find(occupyableHexes.begin(),occupyableHexes.end(),whichOne)!=occupyableHexes.end())// and it's in our range + if (dest->alive()) + { + if(dest->owner != actSt->owner && curInt->cb->battleCanShoot(activeStack, whichOne)) //shooting + { + CCS->curh->changeGraphic(1, 6); //cursor should be changed + giveCommand (BattleAction::SHOOT, whichOne, activeStack->ID); + } + else if(dest->owner != actSt->owner) //attacking + { + const CStack * actStack = activeStack; + int attackFromHex = -1; //hex from which we will attack chosen stack + switch(CCS->curh->number) + { + case 12: //from bottom right + { + bool doubleWide = actStack->doubleWide(); + int destHex = whichOne + ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH : BFIELD_WIDTH+1 ) + + (actStack->attackerOwned && doubleWide ? 1 : 0); + if(vstd::contains(occupyableHexes, destHex)) + attackFromHex = destHex; + else if(actStack->attackerOwned) //if we are attacker + { + if(vstd::contains(occupyableHexes, destHex+1)) + attackFromHex = destHex+1; + } + else //if we are defender + { + if(vstd::contains(occupyableHexes, destHex-1)) + attackFromHex = destHex-1; + } + break; + } + case 7: //from bottom left + { + int destHex = whichOne + ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH-1 : BFIELD_WIDTH ); + if(vstd::contains(occupyableHexes, destHex)) + attackFromHex = destHex; + else if(actStack->attackerOwned) //if we are attacker + { + if(vstd::contains(occupyableHexes, destHex+1)) + attackFromHex = destHex+1; + } + else //if we are defender + { + if(vstd::contains(occupyableHexes, destHex-1)) + attackFromHex = destHex-1; + } + break; + } + case 8: //from left + { + if(actStack->doubleWide() && !actStack->attackerOwned) + { + std::vector acc = curInt->cb->battleGetAvailableHexes(activeStack, false); + if(vstd::contains(acc, whichOne)) + attackFromHex = whichOne - 1; + else + attackFromHex = whichOne - 2; + } + else + { + attackFromHex = whichOne - 1; + } + break; + } + case 9: //from top left + { + int destHex = whichOne - ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH+1 : BFIELD_WIDTH ); + if(vstd::contains(occupyableHexes, destHex)) + attackFromHex = destHex; + else if(actStack->attackerOwned) //if we are attacker + { + if(vstd::contains(occupyableHexes, destHex+1)) + attackFromHex = destHex+1; + } + else //if we are defender + { + if(vstd::contains(occupyableHexes, destHex-1)) + attackFromHex = destHex-1; + } + break; + } + case 10: //from top right + { + bool doubleWide = actStack->doubleWide(); + int destHex = whichOne - ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH : BFIELD_WIDTH-1 ) + + (actStack->attackerOwned && doubleWide ? 1 : 0); + if(vstd::contains(occupyableHexes, destHex)) + attackFromHex = destHex; + else if(actStack->attackerOwned) //if we are attacker + { + if(vstd::contains(occupyableHexes, destHex+1)) + attackFromHex = destHex+1; + } + else //if we are defender + { + if(vstd::contains(occupyableHexes, destHex-1)) + attackFromHex = destHex-1; + } + break; + } + case 11: //from right + { + if(actStack->doubleWide() && actStack->attackerOwned) + { + std::vector acc = curInt->cb->battleGetAvailableHexes(activeStack, false); + if(vstd::contains(acc, whichOne)) + attackFromHex = whichOne + 1; + else + attackFromHex = whichOne + 2; + } + else + { + attackFromHex = whichOne + 1; + } + break; + } + case 13: //from bottom + { + int destHex = whichOne + ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH : BFIELD_WIDTH+1 ); + if(vstd::contains(occupyableHexes, destHex)) + attackFromHex = destHex; + else if(attackingHeroInstance->tempOwner == curInt->cb->getMyColor()) //if we are attacker + { + if(vstd::contains(occupyableHexes, destHex+1)) + attackFromHex = destHex+1; + } + else //if we are defender + { + if(vstd::contains(occupyableHexes, destHex-1)) + attackFromHex = destHex-1; + } + break; + } + case 14: //from top + { + int destHex = whichOne - ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH : BFIELD_WIDTH-1 ); + if(vstd::contains(occupyableHexes, destHex)) + attackFromHex = destHex; + else if(attackingHeroInstance->tempOwner == curInt->cb->getMyColor()) //if we are attacker + { + if(vstd::contains(occupyableHexes, destHex+1)) + attackFromHex = destHex+1; + } + else //if we are defender + { + if(vstd::contains(occupyableHexes, destHex-1)) + attackFromHex = destHex-1; + } + break; + } + } + + if(attackFromHex >= 0) //we can be in this line when unreachable creature is L - clicked (as of revision 1308) + { + giveCommand(BattleAction::WALK_AND_ATTACK, attackFromHex, activeStack->ID, whichOne); + + CCS->curh->changeGraphic(1, 6); //cursor should be changed + } + + } + else if (actSt->hasBonusOfType(Bonus::HEALER) && actSt->owner == dest->owner) //friendly creature we can heal + { //TODO: spellDestSelectMode > -2 if we don't want to heal but perform some other (?) action + giveCommand(BattleAction::STACK_HEAL, whichOne, activeStack->ID); //command healing + + CCS->curh->changeGraphic(1, 6); //cursor should be changed + } + + } //stack is not alive + else if (actSt->hasBonusOfType(Bonus::DAEMON_SUMMONING) && actSt->casts && + actSt->owner == dest->owner && spellSelMode > -2)//friendly body we can (and want) rise + { + giveCommand(BattleAction::DAEMON_SUMMONING, whichOne, activeStack->ID); + + CCS->curh->changeGraphic(1, 6); //cursor should be changed + } + else //not a subject of resurrection + walkableTile = true; + } + else + { + walkableTile = true; + } + + if (walkableTile) // we can try to move to this tile + { + if(std::find(occupyableHexes.begin(), occupyableHexes.end(), whichOne) != occupyableHexes.end())// and it's in our range { CCS->curh->changeGraphic(1, 6); //cursor should be changed if(activeStack->doubleWide()) @@ -2848,186 +3050,20 @@ void CBattleInterface::hexLclicked(int whichOne) std::vector acc = curInt->cb->battleGetAvailableHexes(activeStack, false); int shiftedDest = whichOne + (activeStack->attackerOwned ? 1 : -1); if(vstd::contains(acc, whichOne)) - giveCommand(2,whichOne,activeStack->ID); + giveCommand (BattleAction::WALK ,whichOne, activeStack->ID); else if(vstd::contains(acc, shiftedDest)) - giveCommand(2,shiftedDest,activeStack->ID); + giveCommand (BattleAction::WALK, shiftedDest, activeStack->ID); } else { - giveCommand(2,whichOne,activeStack->ID); + giveCommand(BattleAction::WALK, whichOne, activeStack->ID); } } else if(actSt->hasBonusOfType(Bonus::CATAPULT) && isCatapultAttackable(whichOne)) //attacking (catapult) { - giveCommand(9,whichOne,activeStack->ID); + giveCommand(BattleAction::CATAPULT, whichOne, activeStack->ID); } } - else if(dest->owner != actSt->owner - && curInt->cb->battleCanShoot(activeStack, whichOne) ) //shooting - { - CCS->curh->changeGraphic(1, 6); //cursor should be changed - giveCommand(7,whichOne,activeStack->ID); - } - else if(dest->owner != actSt->owner) //attacking - { - const CStack * actStack = activeStack; - int attackFromHex = -1; //hex from which we will attack chosen stack - switch(CCS->curh->number) - { - case 12: //from bottom right - { - bool doubleWide = actStack->doubleWide(); - int destHex = whichOne + ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH : BFIELD_WIDTH+1 ) + - (actStack->attackerOwned && doubleWide ? 1 : 0); - if(vstd::contains(occupyableHexes, destHex)) - attackFromHex = destHex; - else if(actStack->attackerOwned) //if we are attacker - { - if(vstd::contains(occupyableHexes, destHex+1)) - attackFromHex = destHex+1; - } - else //if we are defender - { - if(vstd::contains(occupyableHexes, destHex-1)) - attackFromHex = destHex-1; - } - break; - } - case 7: //from bottom left - { - int destHex = whichOne + ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH-1 : BFIELD_WIDTH ); - if(vstd::contains(occupyableHexes, destHex)) - attackFromHex = destHex; - else if(actStack->attackerOwned) //if we are attacker - { - if(vstd::contains(occupyableHexes, destHex+1)) - attackFromHex = destHex+1; - } - else //if we are defender - { - if(vstd::contains(occupyableHexes, destHex-1)) - attackFromHex = destHex-1; - } - break; - } - case 8: //from left - { - if(actStack->doubleWide() && !actStack->attackerOwned) - { - std::vector acc = curInt->cb->battleGetAvailableHexes(activeStack, false); - if(vstd::contains(acc, whichOne)) - attackFromHex = whichOne - 1; - else - attackFromHex = whichOne - 2; - } - else - { - attackFromHex = whichOne - 1; - } - break; - } - case 9: //from top left - { - int destHex = whichOne - ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH+1 : BFIELD_WIDTH ); - if(vstd::contains(occupyableHexes, destHex)) - attackFromHex = destHex; - else if(actStack->attackerOwned) //if we are attacker - { - if(vstd::contains(occupyableHexes, destHex+1)) - attackFromHex = destHex+1; - } - else //if we are defender - { - if(vstd::contains(occupyableHexes, destHex-1)) - attackFromHex = destHex-1; - } - break; - } - case 10: //from top right - { - bool doubleWide = actStack->doubleWide(); - int destHex = whichOne - ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH : BFIELD_WIDTH-1 ) + - (actStack->attackerOwned && doubleWide ? 1 : 0); - if(vstd::contains(occupyableHexes, destHex)) - attackFromHex = destHex; - else if(actStack->attackerOwned) //if we are attacker - { - if(vstd::contains(occupyableHexes, destHex+1)) - attackFromHex = destHex+1; - } - else //if we are defender - { - if(vstd::contains(occupyableHexes, destHex-1)) - attackFromHex = destHex-1; - } - break; - } - case 11: //from right - { - if(actStack->doubleWide() && actStack->attackerOwned) - { - std::vector acc = curInt->cb->battleGetAvailableHexes(activeStack, false); - if(vstd::contains(acc, whichOne)) - attackFromHex = whichOne + 1; - else - attackFromHex = whichOne + 2; - } - else - { - attackFromHex = whichOne + 1; - } - break; - } - case 13: //from bottom - { - int destHex = whichOne + ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH : BFIELD_WIDTH+1 ); - if(vstd::contains(occupyableHexes, destHex)) - attackFromHex = destHex; - else if(attackingHeroInstance->tempOwner == curInt->cb->getMyColor()) //if we are attacker - { - if(vstd::contains(occupyableHexes, destHex+1)) - attackFromHex = destHex+1; - } - else //if we are defender - { - if(vstd::contains(occupyableHexes, destHex-1)) - attackFromHex = destHex-1; - } - break; - } - case 14: //from top - { - int destHex = whichOne - ( (whichOne/BFIELD_WIDTH)%2 ? BFIELD_WIDTH : BFIELD_WIDTH-1 ); - if(vstd::contains(occupyableHexes, destHex)) - attackFromHex = destHex; - else if(attackingHeroInstance->tempOwner == curInt->cb->getMyColor()) //if we are attacker - { - if(vstd::contains(occupyableHexes, destHex+1)) - attackFromHex = destHex+1; - } - else //if we are defender - { - if(vstd::contains(occupyableHexes, destHex-1)) - attackFromHex = destHex-1; - } - break; - } - } - - if(attackFromHex >= 0) //we can be in this line when unreachable creature is L - clicked (as of revision 1308) - { - giveCommand(6, attackFromHex, activeStack->ID, whichOne); - - CCS->curh->changeGraphic(1, 6); //cursor should be changed - } - - } - else if (actSt->hasBonusOfType(Bonus::HEALER) && actSt->owner == dest->owner) //friendly creature we can heal - { - giveCommand(12, whichOne, activeStack->ID); //command healing - - CCS->curh->changeGraphic(1, 6); //cursor should be changed - } } } } @@ -3451,6 +3487,7 @@ void CBattleInterface::activateStack() activeStack = stackToActivate; stackToActivate = NULL; const CStack *s = activeStack; + stackSpells.clear(); myTurn = true; if(attackerInt && defenderInt) //hotseat -> need to pick which interface "takes over" as active @@ -3468,8 +3505,18 @@ void CBattleInterface::activateStack() //set casting flag to true if creature can use it to not check it every time if (s->casts && s->hasBonus(Selector::type(Bonus::SPELLCASTER) || Selector::type(Bonus::DAEMON_SUMMONING))) + { stackCanCastSpell = true; + TBonusListPtr bl = s->getBonuses(Selector::type(Bonus::SPELLCASTER)); + BOOST_FOREACH(Bonus * b, *bl) + { + stackSpells.push_back(CGI->spellh->spells[b->subtype]); + } + if (stackSpells.size()) + creatureSpellToCast = stackSpells[111 % stackSpells.size()]; //TODO: randomize? weighted chance? + } + GH.fakeMouseMove(); if(!pendingAnims.size() && !active) diff --git a/client/CBattleInterface.h b/client/CBattleInterface.h index c759e46de..67a554a4f 100644 --- a/client/CBattleInterface.h +++ b/client/CBattleInterface.h @@ -5,6 +5,7 @@ #include #include "GUIBase.h" #include "../lib/CCreatureSet.h" +#include "../lib/ConstTransitivePtr.h" //may be reundant #include "CAnimation.h" /* @@ -419,7 +420,7 @@ class CBattleInterface : public CIntObject { enum SpellSelectionType { - ANY_LOCATION = 0, FRIENDLY_CREATURE, HOSTILE_CREATURE, ANY_CREATURE, OBSTACLE, TELEPORT, NO_LOCATION = -1 + ANY_LOCATION = 0, FRIENDLY_CREATURE, HOSTILE_CREATURE, ANY_CREATURE, OBSTACLE, TELEPORT, NO_LOCATION = -1, STACK_SPELL_CANCELLED = -2 }; private: SDL_Surface * background, * menu, * amountNormal, * amountNegative, * amountPositive, * amountEffNeutral, * cellBorders, * backgroundWithHexes; @@ -456,6 +457,8 @@ private: bool spellDestSelectMode; //if true, player is choosing destination for his spell SpellSelectionType spellSelMode; BattleAction * spellToCast; //spell for which player is choosing destination + CSpell * creatureSpellToCast; + std::vector< ConstTransitivePtr > stackSpells; //all spells possible to cast by the stack void endCastingSpell(); //ends casting spell (eg. when spell has been cast or canceled) void showAliveStack(const CStack *stack, SDL_Surface * to); //helper function for function show diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index a0c2653e0..dc6edfc46 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -632,11 +632,6 @@ void StartAction::applyFirstCl( CClient *cl ) void BattleSpellCast::applyCl( CClient *cl ) { BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleSpellCast,this); - - if(id >= 66 && id <= 69) //elemental summoning - { - BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleNewStackAppeared,GS(cl)->curB->stacks.back()); - } } void SetStackEffect::applyCl( CClient *cl ) @@ -691,6 +686,11 @@ void BattleStacksRemoved::applyCl( CClient *cl ) BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleStacksRemoved, *this); } +void BattleStackAdded::applyCl( CClient *cl ) +{ + BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleNewStackAppeared, GS(cl)->curB->stacks.back()); +} + CGameState* CPackForClient::GS( CClient *cl ) { return cl->gs; diff --git a/global.h b/global.h index 87d496f69..6fd9eaf4e 100644 --- a/global.h +++ b/global.h @@ -327,7 +327,7 @@ namespace SpellCasting }; enum ECastingMode {HERO_CASTING, AFTER_ATTACK_CASTING, //also includes cast before attack - MAGIC_MIRROR}; + MAGIC_MIRROR, CREATURE_ACTIVE_CASTING}; } namespace Buildings diff --git a/lib/BattleAction.h b/lib/BattleAction.h index 0d63ea50a..4df6d6f65 100644 --- a/lib/BattleAction.h +++ b/lib/BattleAction.h @@ -22,10 +22,10 @@ struct DLL_EXPORT BattleAction ui32 stackNumber;//stack ID, -1 left hero, -2 right hero, enum ActionType { - END_TACTIC_PHASE = -2, INVALID = -1, NO_ACTION = 0, HERO_SPELL, WALK, DEFEND, RETREAT, SURRENDER, WALK_AND_ATTACK, SHOOT, WAIT, CATAPULT, MONSTER_SPELL, BAD_MORALE, STACK_HEAL + END_TACTIC_PHASE = -2, INVALID = -1, NO_ACTION = 0, HERO_SPELL, WALK, DEFEND, RETREAT, SURRENDER, WALK_AND_ATTACK, SHOOT, WAIT, CATAPULT, MONSTER_SPELL, BAD_MORALE, + STACK_HEAL, DAEMON_SUMMONING }; si8 actionType; //use ActionType enum for values - //10 = Monster casts a spell (i.e. Faerie Dragons) 11 - Bad morale freeze 12 - stacks heals another stack THex destinationTile; si32 additionalInfo; // e.g. spell number if type is 1 || 10; tile to attack if type is 6 template void serialize(Handler &h, const int version) diff --git a/lib/BattleState.cpp b/lib/BattleState.cpp index 099a50c10..408678b23 100644 --- a/lib/BattleState.cpp +++ b/lib/BattleState.cpp @@ -376,6 +376,41 @@ std::vector BattleInfo::getAccessibility( const CStack * stack, bool addOc return ret; } + +int BattleInfo::getAvaliableHex(TCreature creID, bool attackerOwned, int initialPos) const +{ + int pos; + if (initialPos > -1) + pos = initialPos; + else + { + if (attackerOwned) + pos = 0; //top left + else + pos = BFIELD_WIDTH; //top right + } + + bool ac[BFIELD_SIZE]; + std::set occupyable; + bool twoHex = VLC->creh->creatures[creID]->isDoubleWide(); + bool flying = VLC->creh->creatures[creID]->isFlying();// vstd::contains(VLC->creh->creatures[creID]->bonuses, Bonus::FLYING); + getAccessibilityMap(ac, twoHex, attackerOwned, true, occupyable, flying); + for (int g = pos; -1 < g < BFIELD_SIZE; ) + { + if ((g % BFIELD_WIDTH != 0) && (g % BFIELD_WIDTH != BFIELD_WIDTH-1) && BattleInfo::isAccessible (g, ac, twoHex, attackerOwned, flying, true)) + { + pos = g; + break; + } + if (attackerOwned) + ++g; //probably some more sophisticated range-based iteration is needed + else + --g; + } + + return pos; +} + bool BattleInfo::isStackBlocked(const CStack * stack) const { if(stack->hasBonusOfType(Bonus::SIEGE_WEAPON)) //siege weapons cannot be blocked @@ -1776,7 +1811,7 @@ SpellCasting::ESpellCastProblem BattleInfo::battleCanCastSpell(int player, Spell switch (mode) { - case SpellCasting::HERO_CASTING: + case SpellCasting::HERO_CASTING: { if(castSpells[side] > 0) return SpellCasting::ALREADY_CASTED_THIS_TURN; @@ -1800,7 +1835,7 @@ SpellCasting::ESpellCastProblem BattleInfo::battleCanCastThisSpell( int player, int cside = sides[0] == player ? 0 : 1; //caster's side switch(mode) { - case SpellCasting::HERO_CASTING: + case SpellCasting::HERO_CASTING: { const CGHeroInstance * caster = heroes[cside]; if(!caster->canCastThisSpell(spell)) @@ -1900,7 +1935,10 @@ SpellCasting::ESpellCastProblem BattleInfo::battleCanCastThisSpellHere( int play if(moreGeneralProblem != SpellCasting::OK) return moreGeneralProblem; - return battleIsImmune(getHero(player), spell, mode, dest); + if (mode != SpellCasting::CREATURE_ACTIVE_CASTING) + return battleIsImmune(getHero(player), spell, mode, dest); + else + return battleIsImmune(NULL, spell, mode, dest); } const CGHeroInstance * BattleInfo::getHero( int player ) const diff --git a/lib/BattleState.h b/lib/BattleState.h index c39062294..9eb93286b 100644 --- a/lib/BattleState.h +++ b/lib/BattleState.h @@ -87,6 +87,7 @@ struct DLL_EXPORT BattleInfo : public CBonusSystemNode const CStack * getStackT(THex tileID, bool onlyAlive = true) const; void getAccessibilityMap(bool *accessibility, bool twoHex, bool attackerOwned, bool addOccupiable, std::set & occupyable, bool flying, const CStack* stackToOmmit = NULL) const; //send pointer to at least 187 allocated bytes static bool isAccessible(THex hex, bool * accessibility, bool twoHex, bool attackerOwned, bool flying, bool lastPos); //helper for makeBFS + int getAvaliableHex(TCreature creID, bool attackerOwned, int initialPos = -1) const; //find place for summon / clone effects void makeBFS(THex start, bool*accessibility, THex *predecessor, int *dists, bool twoHex, bool attackerOwned, bool flying, bool fillPredecessors) const; //*accessibility must be prepared bool[187] array; last two pointers must point to the at least 187-elements int arrays - there is written result std::pair< std::vector, int > getPath(THex start, THex dest, bool*accessibility, bool flyingCreature, bool twoHex, bool attackerOwned); //returned value: pair; length may be different than number of elements in path since flying vreatures jump between distant hexes std::vector getAccessibility(const CStack * stack, bool addOccupiable, std::vector * attackable = NULL) const; //returns vector of accessible tiles (taking into account the creature range) diff --git a/lib/IGameCallback.cpp b/lib/IGameCallback.cpp index 6f46b9a95..3da9cbdc0 100644 --- a/lib/IGameCallback.cpp +++ b/lib/IGameCallback.cpp @@ -107,6 +107,18 @@ SpellCasting::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell( con return gs->curB->battleCanCastThisSpell(player, spell, SpellCasting::HERO_CASTING); } +SpellCasting::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell(const CSpell * spell, THex destination) +{ + if(!gs->curB) + { + + tlog1 << "battleCanCastThisSpell called when there is no battle!\n"; + return SpellCasting::NO_HERO_TO_CAST_SPELL; + } + + return gs->curB->battleCanCastThisSpellHere(player, spell, SpellCasting::CREATURE_ACTIVE_CASTING, destination); +} + si8 CBattleInfoCallback::battleGetTacticDist() { if (!gs->curB) diff --git a/lib/IGameCallback.h b/lib/IGameCallback.h index b44d97572..4add7d4e1 100644 --- a/lib/IGameCallback.h +++ b/lib/IGameCallback.h @@ -104,6 +104,7 @@ public: bool battleCanShoot(const CStack * stack, THex dest); //returns true if unit with id ID can shoot to dest bool battleCanCastSpell(); //returns true, if caller can cast a spell SpellCasting::ESpellCastProblem battleCanCastThisSpell(const CSpell * spell); //determines if given spell can be casted (and returns problem description) + SpellCasting::ESpellCastProblem battleCanCastThisSpell(const CSpell * spell, THex destination); //determines if creature can cast a spell here bool battleCanFlee(); //returns true if caller can flee from the battle int battleGetSurrenderCost(); //returns cost of surrendering battle, -1 if surrendering is not possible const CGTownInstance * battleGetDefendedTown(); //returns defended town if current battle is a siege, NULL instead diff --git a/lib/NetPacks.h b/lib/NetPacks.h index 6782d06b0..8d3ad281b 100644 --- a/lib/NetPacks.h +++ b/lib/NetPacks.h @@ -1483,6 +1483,25 @@ struct BattleStacksRemoved : public CPackForClient //3016 } }; +struct BattleStackAdded : public CPackForClient //3017 +{ + BattleStackAdded(){type = 3017;}; + + DLL_EXPORT void applyGs(CGameState *gs); + void applyCl(CClient *cl); + + int attacker; // if true, stack belongs to attacker + int creID; + int amount; + int pos; + int summoned; //if true, remove it afterwards + + template void serialize(Handler &h, const int version) + { + h & attacker & creID & amount & pos & summoned; + } +}; + struct ShowInInfobox : public CPackForClient //107 { ShowInInfobox(){type = 107;}; diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 2cddfa728..87874b53f 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -1019,9 +1019,9 @@ DLL_EXPORT void StartAction::applyGs( CGameState *gs ) case BattleAction::WAIT: st->state.insert(WAITING); return; - case BattleAction::NO_ACTION: case BattleAction::WALK: case BattleAction::WALK_AND_ATTACK: - case BattleAction::SHOOT: case BattleAction::CATAPULT: case BattleAction::MONSTER_SPELL: - case BattleAction::BAD_MORALE: case BattleAction::STACK_HEAL: + case BattleAction::HERO_SPELL: //no change in current stack state + return; + default: //any active stack action - attack, catapult, heal, spell... st->state.insert(MOVED); break; } @@ -1046,7 +1046,8 @@ DLL_EXPORT void BattleSpellCast::applyGs( CGameState *gs ) spellCost = VLC->spellh->spells[id]->costs[skill]; } h->mana -= spellCost; - if(h->mana < 0) h->mana = 0; + if (h->mana < 0) + h->mana = 0; } if(side >= 0 && side < 2 && castedByHero) { @@ -1069,55 +1070,6 @@ DLL_EXPORT void BattleSpellCast::applyGs( CGameState *gs ) } } } - - //elemental summoning - if(id >= 66 && id <= 69) - { - int creID; - switch(id) - { - case 66: - creID = 114; //fire elemental - break; - case 67: - creID = 113; //earth elemental - break; - case 68: - creID = 115; //water elemental - break; - case 69: - creID = 112; //air elemental - break; - } -// const int3 & tile = gs->curB->tile; -// TerrainTile::EterrainType ter = gs->map->terrain[tile.x][tile.y][tile.z].tertype; - - int pos; //position of stack on the battlefield - to be calculated - - bool ac[BFIELD_SIZE]; - std::set occupyable; - bool twoHex = VLC->creh->creatures[creID]->isDoubleWide(); - bool flying = VLC->creh->creatures[creID]->isFlying();// vstd::contains(VLC->creh->creatures[creID]->bonuses, Bonus::FLYING); - gs->curB->getAccessibilityMap(ac, twoHex, !side, true, occupyable, flying); - for(int g=0; ggetPrimSkillLevel(2) * VLC->spellh->spells[id]->powers[skill] * (100 + h->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, id) / 100.0f); //new feature - percentage bonus - CStackInstance *csi = new CStackInstance(creID, summonedElementals); //deleted by d-tor of summoned stack - csi->setArmyObj(h); - CStack * summonedStack = gs->curB->generateNewStack(*csi, gs->curB->stacks.size(), !side, 255, pos); - summonedStack->state.insert(SUMMONED); - summonedStack->attachTo(csi); - summonedStack->postInit(); - //summonedStack->addNewBonus( makeFeature(HeroBonus::SUMMONED, HeroBonus::ONE_BATTLE, 0, 0, HeroBonus::BONUS_FROM_HERO) ); - gs->curB->stacks.push_back(summonedStack); - } } void actualizeEffect(CStack * s, const std::vector & ef) @@ -1308,6 +1260,23 @@ DLL_EXPORT void BattleStacksRemoved::applyGs( CGameState *gs ) } } +DLL_EXPORT void BattleStackAdded::applyGs(CGameState *gs) +{ + if (!THex(pos).isValid()) + { + tlog2 << "No place found for new stack!\n"; + return; + } + CStackInstance *csi = new CStackInstance(creID, amount); + csi->setArmyObj(gs->curB->belligerents[attacker ? 0 : 1]); + CStack * summonedStack = gs->curB->generateNewStack(*csi, gs->curB->stacks.size(), attacker, 255, pos); //TODO: netpacks? + if (summoned) + summonedStack->state.insert(SUMMONED); + summonedStack->attachTo(csi); + summonedStack->postInit(); + gs->curB->stacks.push_back(summonedStack); //the stack is not "SUMMONED", it is permanent +} + DLL_EXPORT void YourTurn::applyGs( CGameState *gs ) { gs->currentPlayer = player; diff --git a/lib/RegisterTypes.cpp b/lib/RegisterTypes.cpp index 0e52d9ac8..0f9c09449 100644 --- a/lib/RegisterTypes.cpp +++ b/lib/RegisterTypes.cpp @@ -156,6 +156,7 @@ void registerTypes2(Serializer &s) s.template registerType(); s.template registerType(); s.template registerType(); + s.template registerType(); s.template registerType(); s.template registerType(); s.template registerType(); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index e2fbbb010..a31a5f495 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -3280,6 +3280,35 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) } + sendAndApply(&end_action); + break; + } + case BattleAction::DAEMON_SUMMONING: + //TODO: From Strategija: + //Summon Demon is a level 2 spell. + //Cloned Pit Lord stack can use the specialty as well. + { + StartAction start_action(ba); + sendAndApply(&start_action); + + CStack *summoner = gs->curB->getStack(ba.stackNumber), + *destStack = gs->curB->getStackT(ba.destinationTile, false); + + BattleStackAdded bsa; + bsa.attacker = summoner->attackerOwned; + + bsa.creID = summoner->getBonus(Selector::type(Bonus::DAEMON_SUMMONING))->subtype; //in case summoner can summon more than one type of monsters... scream! + ui64 risedHp = summoner->count * summoner->valOfBonuses(Bonus::DAEMON_SUMMONING, bsa.creID); + bsa.amount = std::min ((ui32)(risedHp/destStack->MaxHealth()), destStack->baseAmount); + + bsa.pos = gs->curB->getAvaliableHex(bsa.creID, bsa.attacker, destStack->position); + bsa.summoned = false; + + BattleStacksRemoved bsr; //remove body + bsr.stackIDs.insert(destStack->ID); + sendAndApply(&bsr); + sendAndApply(&bsa); + sendAndApply(&end_action); break; } @@ -3673,6 +3702,41 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, THex destinati sendAndApply(&shr); break; } + case 66: + case 67: + case 68: + case 69: + { //elemental summoning + int creID; + switch(spellID) + { + case 66: + creID = 114; //fire elemental + break; + case 67: + creID = 113; //earth elemental + break; + case 68: + creID = 115; //water elemental + break; + case 69: + creID = 112; //air elemental + break; + } + + BattleStackAdded bsa; + + bsa.pos = gs->curB->getAvaliableHex(creID, !(bool)casterSide); //TODO: unify it + + bsa.amount = caster->getPrimSkillLevel(2) * VLC->spellh->spells[spellID]->powers[spellLvl] * + (100 + caster->valOfBonuses(Bonus::SPECIFIC_SPELL_DAMAGE, spellID)) / 100.0f; //new feature - percentage bonus + + bsa.creID = creID; + bsa.attacker = !(bool)casterSide; + bsa.summoned = true; + sendAndApply(&bsa); + } + break; case 64: //remove obstacle { ObstaclesRemoved obr; @@ -3731,6 +3795,7 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, THex destinati sendAndApply(&sc); if(!si.stacks.empty()) //after spellcast info shows sendAndApply(&si); + //Magic Mirror effect if (spell->positiveness < 0 && mode != SpellCasting::MAGIC_MIRROR && spell->level && spell->range[0] == "0") //it is actual spell and can be reflected to single target, no recurrence { @@ -3779,7 +3844,8 @@ bool CGameHandler::makeCustomAction( BattleAction &ba ) } const CSpell *s = VLC->spellh->spells[ba.additionalInfo]; - if (s->mainEffectAnim > -1) //TODO: special effects, like Clone + if (s->mainEffectAnim > -1 || (s->id >= 66 || s->id <= 69)) //allow summon elementals + //TODO: special effects, like Clone { ui8 skill = h->getSpellSchoolLevel(s); //skill level