diff --git a/Global.h b/Global.h index 89aea2b26..676a893de 100644 --- a/Global.h +++ b/Global.h @@ -324,6 +324,16 @@ namespace vstd { return std::unique_ptr(new T(std::forward(arg1), std::forward(arg2))); } + + template + typename Container::const_reference circularAt(const Container &r, size_t index) + { + assert(r.size()); + index %= r.size(); + auto itr = std::begin(r); + std::advance(itr, index); + return *itr; + } } using std::shared_ptr; diff --git a/client/BattleInterface/CBattleAnimations.cpp b/client/BattleInterface/CBattleAnimations.cpp index 2d7c485cd..10e992795 100644 --- a/client/BattleInterface/CBattleAnimations.cpp +++ b/client/BattleInterface/CBattleAnimations.cpp @@ -903,7 +903,7 @@ void CShootingAnimation::endAnim() } CSpellEffectAnimation::CSpellEffectAnimation(CBattleInterface * _owner, ui32 _effect, BattleHex _destTile, int _dx, int _dy, bool _Vflip) -:CBattleAnimation(_owner), effect(_effect), destTile(_destTile), customAnim(""), dx(_dx), dy(_dy), Vflip(_Vflip) +:CBattleAnimation(_owner), effect(_effect), destTile(_destTile), customAnim(""), x(0), y(0), dx(_dx), dy(_dy), Vflip(_Vflip) {} CSpellEffectAnimation::CSpellEffectAnimation(CBattleInterface * _owner, std::string _customAnim, int _x, int _y, int _dx, int _dy, bool _Vflip) diff --git a/client/BattleInterface/CBattleInterface.cpp b/client/BattleInterface/CBattleInterface.cpp index 67ec4576c..905ed51a5 100644 --- a/client/BattleInterface/CBattleInterface.cpp +++ b/client/BattleInterface/CBattleInterface.cpp @@ -341,7 +341,7 @@ CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSe int ID = obst[t].ID; std::string gfxName = obst[t].getInfo().defName; - if(!obst[t].isAbsoluteObstacle) + if(obst[t].obstacleType == CObstacleInstance::USUAL) { idToObstacle[ID] = CDefHandler::giveDef(gfxName); for(size_t n = 0; n < idToObstacle[ID]->ourImages.size(); ++n) @@ -355,6 +355,9 @@ CBattleInterface::CBattleInterface(const CCreatureSet * army1, const CCreatureSe } } + quicksand = CDefHandler::giveDef("C17SPE1.DEF"); + landMine = CDefHandler::giveDef("C09SPF1.DEF"); + for (int i = 0; i < bfield.size(); i++) { children.push_back(&bfield[i]); @@ -423,6 +426,9 @@ CBattleInterface::~CBattleInterface() for(std::map< int, CDefHandler * >::iterator g=idToObstacle.begin(); g!=idToObstacle.end(); ++g) delete g->second; + delete quicksand; + delete landMine; + delete siegeH; //TODO: play AI tracks if battle was during AI turn @@ -636,7 +642,7 @@ void CBattleInterface::show(SDL_Surface * to) for(size_t b = 0; b < obstacles.size(); ++b) { const CObstacleInstance &oi = obstacles[b]; - if(!oi.isAbsoluteObstacle) + if(oi.obstacleType != CObstacleInstance::ABSOLUTE_OBSTACLE) { //BattleHex position = CGI->heroh->obstacles.find(obstacles[b].ID)->second.getMaxBlocked(obstacles[b].pos); hexToObstacle.insert(std::make_pair(oi.pos, b)); @@ -717,8 +723,8 @@ void CBattleInterface::show(SDL_Surface * to) { for(int b = 0; b < GameConstants::BFIELD_SIZE; ++b) //showing alive stacks { - showAliveStacks(stackAliveByHex, b, &flyingStacks, to); showObstacles(&hexToObstacle, obstacles, b, to); + showAliveStacks(stackAliveByHex, b, &flyingStacks, to); } } // Siege drawing @@ -786,8 +792,8 @@ void CBattleInterface::show(SDL_Surface * to) for (int k = xMin; k <= xMax; k++) { int hex = j * 17 + k; - showAliveStacks(stackAliveByHex, hex, &flyingStacks, to); showObstacles(&hexToObstacle, obstacles, hex, to); + showAliveStacks(stackAliveByHex, hex, &flyingStacks, to); showPieceOfWall(to, hex, stacks); } @@ -904,15 +910,9 @@ void CBattleInterface::showObstacles(std::multimap *hexToObstacl for(std::multimap::const_iterator it = obstRange.first; it != obstRange.second; ++it) { CObstacleInstance & curOb = obstacles[it->second]; - std::vector &images = idToObstacle[curOb.ID]->ourImages; //reference to animation of obstacle - Rect r = hexPosition(hex); - int offset = images.front().bitmap->h % 42; - if(curOb.getInfo().blockedTiles.front() < 0) - offset -= 42; - - r.y += 42 - images.front().bitmap->h + offset; - //r.y -= cellShade->h*CGI->heroh->obstacles.find(curOb.ID)->second.height - images.front().bitmap->h; - blitAt(images[((animCount+1)/(4/getAnimSpeed()))%images.size()].bitmap, r.x, r.y, to); + SDL_Surface *toBlit = imageOfObstacle(curOb); + Point p = whereToBlitObstacleImage(toBlit, curOb); + blitAt(toBlit, p.x, p.y, to); } } @@ -1957,12 +1957,12 @@ void CBattleInterface::activateStack() creatureSpellToCast = -1; } - GH.fakeMouseMove(); if(!pendingAnims.size() && !active) activate(); getPossibleActionsForStack (activeStack); + GH.fakeMouseMove(); } double CBattleInterface::getAnimSpeedMultiplier() const @@ -2279,8 +2279,8 @@ void CBattleInterface::redrawBackgroundWithHexes(const CStack * activeStack) //draw absolute obstacles (cliffs and so on) BOOST_FOREACH(const CObstacleInstance &oi, curInt->cb->battleGetAllObstacles()) - if(oi.isAbsoluteObstacle) - blitAt(idToAbsoluteObstacle[oi.ID], oi.getInfo().width, oi.getInfo().height, backgroundWithHexes); + if(oi.obstacleType == CObstacleInstance::ABSOLUTE_OBSTACLE) + blitAt(imageOfObstacle(oi), oi.getInfo().width, oi.getInfo().height, backgroundWithHexes); if(settings["battle"]["cellBorders"].Bool()) CSDL_Ext::blit8bppAlphaTo24bpp(cellBorders, NULL, backgroundWithHexes, NULL); @@ -2418,7 +2418,8 @@ void CBattleInterface::endAction(const BattleAction* action) else attackingHero->setPhase(0); } - if(action->actionType == BattleAction::WALK && creAnims[action->stackNumber]->getType() != 2) //walk or walk & attack + + if(stack && action->actionType == BattleAction::WALK && creAnims[action->stackNumber]->getType() != 2) //walk or walk & attack { pendingAnims.push_back(std::make_pair(new CMovementEndAnimation(this, stack, action->destinationTile), false)); } @@ -3246,6 +3247,77 @@ Rect CBattleInterface::hexPosition(BattleHex hex) const return Rect(x, y, w, h); } +SDL_Surface * CBattleInterface::imageOfObstacle(const CObstacleInstance &oi) const +{ + int frameIndex = (animCount+1) / (40/getAnimSpeed()); + switch(oi.obstacleType) + { + case CObstacleInstance::USUAL: + return vstd::circularAt(idToObstacle.find(oi.ID)->second->ourImages, frameIndex).bitmap; + case CObstacleInstance::ABSOLUTE_OBSTACLE: + return idToAbsoluteObstacle.find(oi.ID)->second; + case CObstacleInstance::QUICKSAND: + return vstd::circularAt(quicksand->ourImages, frameIndex).bitmap; + case CObstacleInstance::LAND_MINE: + return vstd::circularAt(landMine->ourImages, frameIndex).bitmap; + default: + assert(0); + } +} + +void CBattleInterface::obstaclePlaced(const CObstacleInstance & oi) +{ + //so when multiple obstacles are added, they show up one after another + waitForAnims(); + + int effectID = -1; + + switch(oi.obstacleType) + { + case CObstacleInstance::QUICKSAND: + effectID = 55; + break; + case CObstacleInstance::LAND_MINE: + effectID = 47; + break; + //TODO firewall, force field + default: + tlog1 << "I don't know how to animate appearing obstacle of type " << (int)oi.obstacleType << std::endl; + return; + } + + if(graphics->battleACToDef[effectID].empty()) + { + tlog1 << "Cannot find def for effect type " << effectID << std::endl; + return; + } + + std::string defname = graphics->battleACToDef[effectID].front(); + + //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 = whereToBlitObstacleImage(imageOfObstacle(oi), oi); + addNewAnim(new CSpellEffectAnimation(this, defname, whereTo.x, whereTo.y)); +} + +Point CBattleInterface::whereToBlitObstacleImage(SDL_Surface *image, const CObstacleInstance &obstacle) const +{ + int offset = image->h % 42; + if(obstacle.obstacleType == CObstacleInstance::USUAL) + { + if(obstacle.getInfo().blockedTiles.front() < 0) + offset -= 42; + } + else if(obstacle.obstacleType == CObstacleInstance::QUICKSAND) + { + offset -= 42; + } + + Rect r = hexPosition(obstacle.pos); + r.y += 42 - image->h + offset; + return r.topLeft(); +} + std::string CBattleInterface::SiegeHelper::townTypeInfixes[GameConstants::F_NUMBER] = {"CS", "RM", "TW", "IN", "NC", "DN", "ST", "FR", "EL"}; CBattleInterface::SiegeHelper::SiegeHelper(const CGTownInstance *siegeTown, const CBattleInterface * _owner) diff --git a/client/BattleInterface/CBattleInterface.h b/client/BattleInterface/CBattleInterface.h index 659a846cc..aa60c9033 100644 --- a/client/BattleInterface/CBattleInterface.h +++ b/client/BattleInterface/CBattleInterface.h @@ -112,6 +112,9 @@ private: std::map< int, CDefHandler * > idToProjectile; //projectiles of creatures (creatureID, defhandler) std::map< int, CDefHandler * > idToObstacle; //obstacles located on the battlefield std::map< int, SDL_Surface * > idToAbsoluteObstacle; //obstacles located on the battlefield + CDefHandler *landMine; + CDefHandler *quicksand; + std::map< int, bool > creDir; // ui8 animCount; const CStack * activeStack; //number of active stack; NULL - no one @@ -153,6 +156,8 @@ private: void showAliveStacks(std::vector *aliveStacks, int hex, std::vector *flyingStacks, SDL_Surface *to); // loops through all stacks at a given hex position void showPieceOfWall(SDL_Surface * to, int hex, const std::vector & stacks); //helper function for show void showObstacles(std::multimap *hexToObstacle, std::vector &obstacles, int hex, SDL_Surface *to); // show all obstacles at a given hex position + SDL_Surface *imageOfObstacle(const CObstacleInstance &oi) const; + Point whereToBlitObstacleImage(SDL_Surface *image, const CObstacleInstance &obstacle) const; void redrawBackgroundWithHexes(const CStack * activeStack); void printConsoleAttacked(const CStack * defender, int dmg, int killed, const CStack * attacker, bool Multiple); @@ -270,7 +275,8 @@ public: bool canStackMoveHere (const CStack * sactive, BattleHex MyNumber); //TODO: move to BattleState / callback BattleHex fromWhichHexAttack(BattleHex myNumber); - + void obstaclePlaced(const CObstacleInstance & oi); + friend class CPlayerInterface; friend class CAdventureMapButton; friend class CInGameConsole; diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index ba917f988..18b59cf98 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -910,6 +910,17 @@ void CPlayerInterface::battleAttack(const BattleAttack *ba) } } +void CPlayerInterface::battleObstaclesPlaced(const CObstacleInstance &obstacle) +{ + EVENT_HANDLER_CALLED_BY_CLIENT; + if(LOCPLINT != this) + { //another local interface should do this + return; + } + + battleInt->obstaclePlaced(obstacle); +} + void CPlayerInterface::yourTacticPhase(int distance) { THREAD_CREATED_BY_CLIENT; diff --git a/client/CPlayerInterface.h b/client/CPlayerInterface.h index bb4ccc4fe..e5411d610 100644 --- a/client/CPlayerInterface.h +++ b/client/CPlayerInterface.h @@ -197,6 +197,7 @@ public: void battleObstaclesRemoved(const std::set & removedObstacles) OVERRIDE; //called when a certain set of obstacles is removed from batlefield; IDs of them are given void battleCatapultAttacked(const CatapultAttack & ca) OVERRIDE; //called when catapult makes an attack void battleStacksRemoved(const BattleStacksRemoved & bsr) OVERRIDE; //called when certain stack is completely removed from battlefield + void battleObstaclePlaced(const CObstacleInstance &obstacle) OVERRIDE; void yourTacticPhase(int distance) OVERRIDE; //-------------// diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index 4c5724ffe..49602a3f2 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -595,6 +595,11 @@ void BattleTriggerEffect::applyCl(CClient * cl) BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleTriggerEffect, *this); } +void BattleObstaclePlaced::applyCl(CClient * cl) +{ + BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleObstaclesPlaced, obstacle); +} + void BattleResult::applyFirstCl( CClient *cl ) { BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleEnd,this); diff --git a/lib/BattleState.cpp b/lib/BattleState.cpp index 0e395ac57..13b43fa7b 100644 --- a/lib/BattleState.cpp +++ b/lib/BattleState.cpp @@ -229,6 +229,8 @@ bool BattleInfo::isAccessible(BattleHex hex, bool * accessibility, bool twoHex, void BattleInfo::makeBFS(BattleHex start, bool *accessibility, BattleHex *predecessor, int *dists, bool twoHex, bool attackerOwned, bool flying, bool fillPredecessors) const //both pointers must point to the at least 187-elements int arrays { + std::set quicksands = getQuicksands(!attackerOwned); + //inits for(int b=0; b curHex = hexq.front(); std::vector neighbours = curHex.first.neighbouringTiles(); hexq.pop(); + if(curHex.first != start && !flying && vstd::contains(quicksands, curHex.first)) //walking stack can't step past the quicksands + continue; + for(ui32 nr=0; nr=dists[curNext]) @@ -1808,7 +1813,7 @@ BattleInfo * BattleInfo::setupBattle( int3 tile, int terrain, int terType, const try { CObstacleInstance coi; - coi.isAbsoluteObstacle = true; + coi.obstacleType = CObstacleInstance::ABSOLUTE_OBSTACLE; coi.ID = obidgen.getSuchNumber(appropriateAbsoluteObstacle); coi.uniqueID = curB->obstacles.size(); curB->obstacles.push_back(coi); @@ -1819,7 +1824,7 @@ BattleInfo * BattleInfo::setupBattle( int3 tile, int terrain, int terType, const } catch(RangeGenerator::ExhaustedPossibilities &) { - //silently ignore, if we can't place absolute obstacle, we'll go wityh the usual ones + //silently ignore, if we can't place absolute obstacle, we'll go with the usual ones } } @@ -2141,7 +2146,7 @@ ESpellCastProblem::ESpellCastProblem BattleInfo::battleCanCastThisSpellHere( int { if(!deadStack && !aliveStack) return ESpellCastProblem::NO_APPROPRIATE_TARGET; - if(spell->id == Spells::ANIMATE_DEAD && !deadStack->hasBonusOfType(Bonus::UNDEAD)) + if(spell->id == Spells::ANIMATE_DEAD && deadStack && !deadStack->hasBonusOfType(Bonus::UNDEAD)) return ESpellCastProblem::NO_APPROPRIATE_TARGET; if(deadStack && deadStack->owner != player) //you can resurrect only your own stacks //FIXME: it includes alive stacks as well return ESpellCastProblem::NO_APPROPRIATE_TARGET; @@ -2577,6 +2582,20 @@ int BattleInfo::battlefieldTypeToBI(int bfieldType) return BattlefieldBI::NONE; } +std::set BattleInfo::getQuicksands(bool whichSidePerspective) const +{ + std::set ret; + BOOST_FOREACH(const CObstacleInstance &oi, obstacles) + { + if(oi.obstacleType == CObstacleInstance::QUICKSAND + && oi.visibleForSide(whichSidePerspective)) //quicksands are visible to the caster or if owned unit stepped into that partcular patch + { + range::copy(oi.getAffectedTiles(), std::inserter(ret, ret.begin())); + } + } + return ret; +} + CStack::CStack(const CStackInstance *Base, int O, int I, bool AO, int S) : base(Base), ID(I), owner(O), slot(S), attackerOwned(AO), counterAttacks(1) diff --git a/lib/BattleState.h b/lib/BattleState.h index 156d78c7f..c6c8db16f 100644 --- a/lib/BattleState.h +++ b/lib/BattleState.h @@ -98,8 +98,10 @@ struct DLL_LINKAGE BattleInfo : public CBonusSystemNode std::pair< std::vector, int > getPath(BattleHex start, BattleHex 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, bool forPassingBy = false) const; //returns vector of accessible tiles (taking into account the creature range) + bool isObstacleVisibleForSide(const CObstacleInstance &obstacle, ui8 side) const; bool isObstacleOnTile(BattleHex tile) const; bool isStackBlocked(const CStack * stack) const; //returns true if there is neighboring enemy stack + std::set getQuicksands(bool whichSidePerspective) const; ui32 calculateDmg(const CStack* attacker, const CStack* defender, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg); //charge - number of hexes travelled before attack (for champion's jousting) TDmgRange calculateDmgRange(const CStack* attacker, const CStack* defender, TQuantity attackerCount, TQuantity defenderCount, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg) const; //charge - number of hexes travelled before attack (for champion's jousting); returns pair diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index b6b5e53b2..5a4fe10c5 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -1578,14 +1578,16 @@ void CGameState::init(StartInfo * si) map->checkForObjectives(); //needs to be run when all objects are properly placed + int seedAfterInit = ran(); + tlog0 << "Seed after init is " << seedAfterInit << " (before was " << scenarioOps->seedToBeUsed << ")" << std::endl; if(scenarioOps->seedPostInit > 0) { - int actualSeed = ran(); - assert(scenarioOps->seedPostInit == actualSeed); //RNG must be in the same state on all machines when initialization is done (otherwise we have desync) + //RNG must be in the same state on all machines when initialization is done (otherwise we have desync) + assert(scenarioOps->seedPostInit == seedAfterInit); } else { - scenarioOps->seedPostInit = ran(); //store the post init "seed" + scenarioOps->seedPostInit = seedAfterInit; //store the post init "seed" } } diff --git a/lib/CObstacleInstance.cpp b/lib/CObstacleInstance.cpp index e2d974ec8..ba9129ef5 100644 --- a/lib/CObstacleInstance.cpp +++ b/lib/CObstacleInstance.cpp @@ -5,18 +5,69 @@ CObstacleInstance::CObstacleInstance() { - isAbsoluteObstacle = false; + obstacleType = USUAL; + casterSide = -1; + spellLevel = -1; + turnsRemaining = -1; + visibleForAnotherSide = -1; } const CObstacleInfo & CObstacleInstance::getInfo() const { - if(isAbsoluteObstacle) + switch(obstacleType) + { + case ABSOLUTE_OBSTACLE: return VLC->heroh->absoluteObstacles[ID]; + case USUAL: + return VLC->heroh->obstacles[ID]; + default: + assert(0); + } - return VLC->heroh->obstacles[ID]; } std::vector CObstacleInstance::getBlocked() const { - return getInfo().getBlocked(pos); -} \ No newline at end of file + switch(obstacleType) + { + case ABSOLUTE_OBSTACLE: + case USUAL: + return getInfo().getBlocked(pos); + //TODO Force Field + } + + return std::vector(); +} + +std::vector CObstacleInstance::getAffectedTiles() const +{ + switch(obstacleType) + { + case QUICKSAND: + case LAND_MINE: + return std::vector(1, pos); + //TODO Fire Wall + } + return std::vector(); +} + +bool CObstacleInstance::spellGenerated() const +{ + if(obstacleType == USUAL || obstacleType == ABSOLUTE_OBSTACLE) + return false; + + return true; +} + +bool CObstacleInstance::visibleForSide(ui8 side) const +{ + switch(obstacleType) + { + case ABSOLUTE_OBSTACLE: + case USUAL: + return true; + default: + //we hide mines and not discovered quicksands + return casterSide == side || visibleForAnotherSide; + } +} diff --git a/lib/CObstacleInstance.h b/lib/CObstacleInstance.h index 9a5fcaccb..b06fbe670 100644 --- a/lib/CObstacleInstance.h +++ b/lib/CObstacleInstance.h @@ -9,15 +9,31 @@ struct DLL_LINKAGE CObstacleInstance si32 ID; //ID of obstacle (defines type of it) BattleHex pos; //position on battlefield - ui8 isAbsoluteObstacle; //if true, then position is meaningless + enum EObstacleType + { + //ABSOLUTE needs an underscore because it's a Win + USUAL, ABSOLUTE_OBSTACLE, QUICKSAND, LAND_MINE, FORCE_FIELD, FIRE_WALL + }; + + ui8 obstacleType; //if true, then position is meaningless + + //used only for spell-created obtsacles + si8 turnsRemaining; + si8 spellLevel; + si8 casterSide; //0 - obstacle created by attacker; 1 - by defender + si8 visibleForAnotherSide; CObstacleInstance(); - - const CObstacleInfo &getInfo() const; + bool spellGenerated() const; + const CObstacleInfo &getInfo() const; //allowed only when not generated by spell (usual or absolute) std::vector getBlocked() const; + std::vector getAffectedTiles() const; //to be used with quicksands / fire walls /land mines + + bool visibleForSide(ui8 side) const; //0 attacker template void serialize(Handler &h, const int version) { - h & ID & pos & isAbsoluteObstacle & uniqueID; + h & ID & pos & obstacleType & uniqueID; + h & turnsRemaining & spellLevel & casterSide & visibleForAnotherSide; } }; \ No newline at end of file diff --git a/lib/IGameCallback.cpp b/lib/IGameCallback.cpp index 7488bb50d..a91951ded 100644 --- a/lib/IGameCallback.cpp +++ b/lib/IGameCallback.cpp @@ -208,10 +208,17 @@ int CBattleInfoCallback::battleGetBattlefieldType() std::vector CBattleInfoCallback::battleGetAllObstacles() { //boost::shared_lock lock(*gs->mx); + std::vector ret; if(gs->curB) - return gs->curB->obstacles; - else - return std::vector(); + { + BOOST_FOREACH(const CObstacleInstance &oi, gs->curB->obstacles) + { + if(player < 0 || oi.visibleForSide(battleGetMySide())) + ret.push_back(oi); + } + } + + return ret; } const CStack* CBattleInfoCallback::battleGetStackByID(int ID, bool onlyAlive) @@ -405,15 +412,24 @@ const CGHeroInstance * CBattleInfoCallback::battleGetFightingHero(ui8 side) cons return gs->curB->heroes[side]; } -bool CBattleInfoCallback::battleIsBlockedByObstacle(BattleHex tile) +unique_ptr CBattleInfoCallback::battleGetObstacleOnPos(BattleHex tile, bool onlyBlocking /*= true*/) { if(!gs->curB) - return 0; + return NULL; - return gs->curB->isObstacleOnTile(tile); + + BOOST_FOREACH(const CObstacleInstance &obs, battleGetAllObstacles()) + { + if(vstd::contains(obs.getBlocked(), tile) + || (!onlyBlocking && vstd::contains(obs.getAffectedTiles(), tile))) + { + return make_unique(obs); + } + } + + return NULL; } - CGameState *const CPrivilagedInfoCallback::gameState () { return gs; diff --git a/lib/IGameCallback.h b/lib/IGameCallback.h index 9a99ce7b5..e3ff3723e 100644 --- a/lib/IGameCallback.h +++ b/lib/IGameCallback.h @@ -95,7 +95,7 @@ public: //battle int battleGetBattlefieldType(); // 1. sand/shore 2. sand/mesas 3. dirt/birches 4. dirt/hills 5. dirt/pines 6. grass/hills 7. grass/pines 8. lava 9. magic plains 10. snow/mountains 11. snow/trees 12. subterranean 13. swamp/trees 14. fiery fields 15. rock lands 16. magic clouds 17. lucid pools 18. holy ground 19. clover field 20. evil fog 21. "favourable winds" text on magic plains background 22. cursed ground 23. rough 24. ship to ship 25. ship //int battleGetObstaclesAtTile(BattleHex tile); //returns bitfield - bool battleIsBlockedByObstacle(BattleHex tile); + unique_ptr battleGetObstacleOnPos(BattleHex tile, bool onlyBlocking = true); std::vector battleGetAllObstacles(); //returns all obstacles on the battlefield const CStack * battleGetStackByID(int ID, bool onlyAlive = true); //returns stack info by given ID const CStack * battleGetStackByPos(BattleHex pos, bool onlyAlive = true); //returns stack info by given pos diff --git a/lib/IGameEventsReceiver.h b/lib/IGameEventsReceiver.h index 2776b7499..4909466a9 100644 --- a/lib/IGameEventsReceiver.h +++ b/lib/IGameEventsReceiver.h @@ -34,7 +34,7 @@ struct BattleAttack; struct SetStackEffect; struct BattleTriggerEffect; class CComponent; - +struct CObstacleInstance; class DLL_LINKAGE IBattleEventsReceiver { @@ -56,6 +56,7 @@ public: virtual void battleObstaclesRemoved(const std::set & removedObstacles){}; //called when a certain set of obstacles is removed from batlefield; IDs of them are given virtual void battleCatapultAttacked(const CatapultAttack & ca){}; //called when catapult makes an attack virtual void battleStacksRemoved(const BattleStacksRemoved & bsr){}; //called when certain stack is completely removed from battlefield + virtual void battleObstaclePlaced(const CObstacleInstance &obstacle){}; }; class DLL_LINKAGE IGameEventsReceiver diff --git a/lib/NetPacks.h b/lib/NetPacks.h index 4709e4926..a161e9042 100644 --- a/lib/NetPacks.h +++ b/lib/NetPacks.h @@ -10,6 +10,7 @@ #include "ConstTransitivePtr.h" #include "int3.h" #include "ResourceSet.h" +#include "CObstacleInstance.h" /* * NetPacks.h, part of VCMI engine @@ -1592,6 +1593,22 @@ struct BattleTriggerEffect : public CPackForClient //3019 } }; +struct BattleObstaclePlaced : public CPackForClient //3020 +{ //activated at the beginning of turn + BattleObstaclePlaced(){type = 3020;}; + + DLL_LINKAGE void applyGs(CGameState *gs); //effect + void applyCl(CClient *cl); //play animations & stuff + + CObstacleInstance obstacle; + + template void serialize(Handler &h, const int version) + { + h & obstacle; + } +}; + + struct ShowInInfobox : public CPackForClient //107 { ShowInInfobox(){type = 107;}; diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 14f45b047..212cf8493 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -975,6 +975,11 @@ DLL_LINKAGE void BattleTriggerEffect::applyGs( CGameState *gs ) } } +DLL_LINKAGE void BattleObstaclePlaced::applyGs( CGameState *gs ) +{ + gs->curB->obstacles.push_back(obstacle); +} + void BattleResult::applyGs( CGameState *gs ) { //stack with SUMMONED flag but coming from garrison -> most likely resurrected, needs to be removed @@ -1015,7 +1020,20 @@ void BattleResult::applyGs( CGameState *gs ) void BattleStackMoved::applyGs( CGameState *gs ) { - gs->curB->getStack(stack)->position = tilesToMove.back(); + CStack *s = gs->curB->getStack(stack); + BattleHex dest = tilesToMove.back(); + + //if unit ended movement on quicksands that were created by enemy, that quicksand patch becomes visible for owner + BOOST_FOREACH(CObstacleInstance &oi, gs->curB->obstacles) + { + if(oi.obstacleType == CObstacleInstance::QUICKSAND + && vstd::contains(oi.getAffectedTiles(), tilesToMove.back()) + && oi.casterSide != !s->attackerOwned) + { + oi.visibleForAnotherSide = true; + } + } + s->position = dest; } DLL_LINKAGE void BattleStackAttacked::applyGs( CGameState *gs ) diff --git a/lib/RegisterTypes.h b/lib/RegisterTypes.h index 2417da146..2f2ef5382 100644 --- a/lib/RegisterTypes.h +++ b/lib/RegisterTypes.h @@ -152,6 +152,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 147ab7309..0ed73e31e 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -858,11 +858,26 @@ int CGameHandler::moveStack(int stack, BattleHex dest) else //for non-flying creatures { // send one package with the creature path information + + unique_ptr obstacle = NULL; //obstacle that interrupted movement std::vector tiles; int tilesToMove = std::max((int)(path.first.size() - creSpeed), 0); - for(int v=path.first.size()-1; v>=tilesToMove; --v) + int v = path.first.size()-1; + +startWalking: + for(; v >= tilesToMove; --v) { - tiles.push_back(path.first[v]); + BattleHex hex = path.first[v]; + tiles.push_back(hex); + + //if there are quicksands, we stop + if(obstacle = battleGetObstacleOnPos(hex, false)) + { + if(!obstacle->obstacleType == CObstacleInstance::LAND_MINE || obstacle->casterSide != !curStack->attackerOwned) //if it's mine, make boom only if enemy planted it + break; + else + obstacle = NULL; + } } if (tiles.size() > 0) @@ -874,6 +889,34 @@ int CGameHandler::moveStack(int stack, BattleHex dest) sm.tilesToMove = tiles; sendAndApply(&sm); } + + if(obstacle && obstacle->obstacleType == CObstacleInstance::LAND_MINE) + { + //TODO make POOF and deal dmg + + BattleStackAttacked bsa; + bsa.flags |= BattleStackAttacked::EFFECT; + bsa.effect = 82; + bsa.damageAmount = 50; //TODO TODO TODO + bsa.stackAttacked = curStack->ID; + bsa.attackerID = -1; + curStack->prepareAttacked(bsa); + //sendAndApply(&bsa); + StacksInjured si; + si.stacks.push_back(bsa); + sendAndApply(&si); + + ObstaclesRemoved or; + or.obstacles.insert(obstacle->uniqueID); + sendAndApply(&or); + + //if stack didn't die in explosion, continue movement + if(curStack->alive()) + { + tiles.clear(); + goto startWalking; //TODO it's so evil + } + } } return ret; @@ -3685,7 +3728,52 @@ void CGameHandler::handleSpellCasting( int spellID, int spellLvl, BattleHex dest //applying effects switch (spellID) - { //damage spells + { + case Spells::QUICKSAND: + case Spells::LAND_MINE: + { + + const int baseUniqueID = gs->curB->obstacles.size() + ? (gs->curB->obstacles.back().uniqueID+1) + : 0; + + + std::vector availableTiles; + for(int i = 0; i < GameConstants::BFIELD_SIZE; i += 1) + { + BattleHex hex = i; + if(hex.getX() > 2 && hex.getX() < 14 && !battleGetStackByPos(hex, false) & !battleGetObstacleOnPos(hex, false)) + availableTiles.push_back(hex); + } + range::random_shuffle(availableTiles); + + const int patchesForSkill[] = {4, 4, 6, 8}; + int patchesToPut = patchesForSkill[spellLvl]; + vstd::amin(patchesToPut, availableTiles.size()); + for (int i = 0; i < patchesToPut; i++) + { + CObstacleInstance coi; + coi.pos = availableTiles[i]; + coi.casterSide = casterSide; + coi.obstacleType = (spellID == Spells::QUICKSAND) + ? CObstacleInstance::QUICKSAND + : CObstacleInstance::LAND_MINE; + coi.ID = spellID; + coi.spellLevel = spellLvl; + coi.turnsRemaining = -1; + coi.uniqueID = baseUniqueID + i; + coi.visibleForAnotherSide = false; + + BattleObstaclePlaced bop; + bop.obstacle = coi; + sendAndApply(&bop); + } + + } + + break; + + //damage spells case Spells::MAGIC_ARROW: case Spells::ICE_BOLT: case Spells::LIGHTNING_BOLT: