1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-26 22:57:00 +02:00

Merge pull request #181 from vcmi/feature/drawbridgeMechanics

Feature/drawbridge mechanics
This commit is contained in:
ArseniyShestakov 2016-02-14 16:32:24 +03:00
commit c550484613
18 changed files with 423 additions and 86 deletions

View File

@ -108,7 +108,7 @@ BattleAction CStupidAI::activeStack( const CStack * stack )
if(stack->type->idNumber == CreatureID::CATAPULT) if(stack->type->idNumber == CreatureID::CATAPULT)
{ {
BattleAction attack; BattleAction attack;
static const std::vector<int> wallHexes = {50, 183, 182, 130, 62, 29, 12, 95}; static const std::vector<int> wallHexes = {50, 183, 182, 130, 78, 29, 12, 95};
attack.destinationTile = *RandomGeneratorUtil::nextItem(wallHexes, CRandomGenerator::getDefault()); attack.destinationTile = *RandomGeneratorUtil::nextItem(wallHexes, CRandomGenerator::getDefault());
attack.actionType = Battle::CATAPULT; attack.actionType = Battle::CATAPULT;

View File

@ -11,6 +11,9 @@ GENERAL:
* New cheat code: * New cheat code:
- vcmiglaurung - gives 5000 crystal dragons into each slot - vcmiglaurung - gives 5000 crystal dragons into each slot
BATTLES:
* Drawbridge mechanics implemented (animation still missing)
ADVETURE AI: ADVETURE AI:
* Fixed AI trying to go through underground rock * Fixed AI trying to go through underground rock
* Fixed several cases causing AI wandering aimlessly * Fixed several cases causing AI wandering aimlessly

View File

@ -1012,6 +1012,14 @@ void CPlayerInterface::battleObstaclePlaced(const CObstacleInstance &obstacle)
battleInt->obstaclePlaced(obstacle); battleInt->obstaclePlaced(obstacle);
} }
void CPlayerInterface::battleGateStateChanged(const EGateState state)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
BATTLE_EVENT_POSSIBLE_RETURN;
battleInt->gateStateChanged(state);
}
void CPlayerInterface::yourTacticPhase(int distance) void CPlayerInterface::yourTacticPhase(int distance)
{ {
THREAD_CREATED_BY_CLIENT; THREAD_CREATED_BY_CLIENT;

View File

@ -220,6 +220,7 @@ public:
void battleCatapultAttacked(const CatapultAttack & ca) override; //called when catapult makes an attack 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 battleStacksRemoved(const BattleStacksRemoved & bsr) override; //called when certain stack is completely removed from battlefield
void battleObstaclePlaced(const CObstacleInstance &obstacle) override; void battleObstaclePlaced(const CObstacleInstance &obstacle) override;
void battleGateStateChanged(const EGateState state) override;
void yourTacticPhase(int distance) override; void yourTacticPhase(int distance) override;
//-------------// //-------------//

View File

@ -658,6 +658,11 @@ void BattleObstaclePlaced::applyCl(CClient * cl)
BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleObstaclePlaced, *obstacle); BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleObstaclePlaced, *obstacle);
} }
void BattleUpdateGateState::applyFirstCl(CClient * cl)
{
BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleGateStateChanged, state);
}
void BattleResult::applyFirstCl( CClient *cl ) void BattleResult::applyFirstCl( CClient *cl )
{ {
BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleEnd,this); BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleEnd,this);

View File

@ -1218,9 +1218,14 @@ void CBattleInterface::stackIsCatapulting(const CatapultAttack & ca)
for(auto attackInfo : ca.attackedParts) for(auto attackInfo : ca.attackedParts)
{ {
SDL_FreeSurface(siegeH->walls[attackInfo.attackedPart + 2]); int wallId = attackInfo.attackedPart + 2;
siegeH->walls[attackInfo.attackedPart + 2] = BitmapHandler::loadBitmap( //gate state changing handled separately
siegeH->getSiegeName(attackInfo.attackedPart + 2, curInt->cb->battleGetWallState(attackInfo.attackedPart))); if(wallId == SiegeHelper::GATE)
continue;
SDL_FreeSurface(siegeH->walls[wallId]);
siegeH->walls[wallId] = BitmapHandler::loadBitmap(
siegeH->getSiegeName(wallId, curInt->cb->battleGetWallState(attackInfo.attackedPart)));
} }
} }
@ -2735,6 +2740,37 @@ void CBattleInterface::obstaclePlaced(const CObstacleInstance & oi)
//CCS->soundh->playSound(sound); //CCS->soundh->playSound(sound);
} }
void CBattleInterface::gateStateChanged(const EGateState state)
{
auto oldState = curInt->cb->battleGetGateState();
bool playSound = false;
int stateId = EWallState::NONE;
switch(state)
{
case EGateState::CLOSED:
if(oldState != EGateState::BLOCKED)
playSound = true;
break;
case EGateState::BLOCKED:
if(oldState != EGateState::CLOSED)
playSound = true;
break;
case EGateState::OPENED:
playSound = true;
stateId = EWallState::DAMAGED;
break;
case EGateState::DESTROYED:
stateId = EWallState::DESTROYED;
break;
}
SDL_FreeSurface(siegeH->walls[SiegeHelper::GATE]);
if(stateId != EWallState::NONE)
siegeH->walls[SiegeHelper::GATE] = BitmapHandler::loadBitmap(siegeH->getSiegeName(SiegeHelper::GATE, stateId));
if(playSound)
CCS->soundh->playSound(soundBase::DRAWBRG);
}
const CGHeroInstance * CBattleInterface::currentHero() const const CGHeroInstance * CBattleInterface::currentHero() const
{ {
if(attackingHeroInstance->tempOwner == curInt->playerID) if(attackingHeroInstance->tempOwner == curInt->playerID)
@ -2834,75 +2870,83 @@ std::string CBattleInterface::SiegeHelper::getSiegeName(ui16 what, int state) co
switch(what) switch(what)
{ {
case 0: //background case SiegeHelper::BACKGROUND:
return prefix + "BACK.BMP"; return prefix + "BACK.BMP";
case 1: //background wall case SiegeHelper::BACKGROUND_WALL:
{ {
switch(town->town->faction->index) switch(town->town->faction->index)
{ {
case 5: case 4: case 1: case 6: case ETownType::RAMPART:
case ETownType::NECROPOLIS:
case ETownType::DUNGEON:
case ETownType::STRONGHOLD:
return prefix + "TPW1.BMP"; return prefix + "TPW1.BMP";
default: default:
return prefix + "TPWL.BMP"; return prefix + "TPWL.BMP";
} }
} }
case 2: //keep case SiegeHelper::KEEP:
return prefix + "MAN" + addit + ".BMP"; return prefix + "MAN" + addit + ".BMP";
case 3: //bottom tower case SiegeHelper::BOTTOM_TOWER:
return prefix + "TW1" + addit + ".BMP"; return prefix + "TW1" + addit + ".BMP";
case 4: //bottom wall case SiegeHelper::BOTTOM_WALL:
return prefix + "WA1" + addit + ".BMP"; return prefix + "WA1" + addit + ".BMP";
case 5: //below gate case SiegeHelper::WALL_BELLOW_GATE:
return prefix + "WA3" + addit + ".BMP"; return prefix + "WA3" + addit + ".BMP";
case 6: //over gate case SiegeHelper::WALL_OVER_GATE:
return prefix + "WA4" + addit + ".BMP"; return prefix + "WA4" + addit + ".BMP";
case 7: //upper wall case SiegeHelper::UPPER_WALL:
return prefix + "WA6" + addit + ".BMP"; return prefix + "WA6" + addit + ".BMP";
case 8: //upper tower case SiegeHelper::UPPER_TOWER:
return prefix + "TW2" + addit + ".BMP"; return prefix + "TW2" + addit + ".BMP";
case 9: //gate case SiegeHelper::GATE:
return prefix + "DRW" + addit + ".BMP"; return prefix + "DRW" + addit + ".BMP";
case 10: //gate arch case SiegeHelper::GATE_ARCH:
return prefix + "ARCH.BMP"; return prefix + "ARCH.BMP";
case 11: //bottom static wall case SiegeHelper::BOTTOM_STATIC_WALL:
return prefix + "WA2.BMP"; return prefix + "WA2.BMP";
case 12: //upper static wall case SiegeHelper::UPPER_STATIC_WALL:
return prefix + "WA5.BMP"; return prefix + "WA5.BMP";
case 13: //moat case SiegeHelper::MOAT:
return prefix + "MOAT.BMP"; return prefix + "MOAT.BMP";
case 14: //mlip case SiegeHelper::BACKGROUND_MOAT:
return prefix + "MLIP.BMP"; return prefix + "MLIP.BMP";
case 15: //keep creature cover case SiegeHelper::KEEP_BATTLEMENT:
return prefix + "MANC.BMP"; return prefix + "MANC.BMP";
case 16: //bottom turret creature cover case SiegeHelper::BOTTOM_BATTLEMENT:
return prefix + "TW1C.BMP"; return prefix + "TW1C.BMP";
case 17: //upper turret creature cover case SiegeHelper::UPPER_BATTLEMENT:
return prefix + "TW2C.BMP"; return prefix + "TW2C.BMP";
default: default:
return ""; return "";
} }
} }
/// What: 1. background wall, 2. keep, 3. bottom tower, 4. bottom wall, 5. wall below gate,
/// 6. wall over gate, 7. upper wall, 8. upper tower, 9. gate, 10. gate arch, 11. bottom static wall, 12. upper static wall, 13. moat, 14. mlip,
/// 15. keep turret cover, 16. lower turret cover, 17. upper turret cover
void CBattleInterface::SiegeHelper::printPartOfWall(SDL_Surface * to, int what) void CBattleInterface::SiegeHelper::printPartOfWall(SDL_Surface * to, int what)
{ {
Point pos = Point(-1, -1); Point pos = Point(-1, -1);
auto & ci = owner->siegeH->town->town->clientInfo; auto & ci = town->town->clientInfo;
if (what >= 1 && what <= 17) if (vstd::iswithin(what, 1, 17))
{ {
pos.x = ci.siegePositions[what].x + owner->pos.x; pos.x = ci.siegePositions[what].x + owner->pos.x;
pos.y = ci.siegePositions[what].y + owner->pos.y; pos.y = ci.siegePositions[what].y + owner->pos.y;
} }
if (town->town->faction->index == ETownType::TOWER if (town->town->faction->index == ETownType::TOWER
&& (what == 13 || what == 14)) && (what == SiegeHelper::MOAT || what == SiegeHelper::BACKGROUND_MOAT))
return; // no moat in Tower. TODO: remove hardcode somehow? return; // no moat in Tower. TODO: remove hardcode somehow?
if(pos.x != -1) if(pos.x != -1)
{ {
//gate have no displayed bitmap when drawbridge is raised
if(what == SiegeHelper::GATE)
{
auto gateState = owner->curInt->cb->battleGetGateState();
if(gateState != EGateState::OPENED && gateState != EGateState::DESTROYED)
return;
}
blitAt(walls[what], pos.x, pos.y, to); blitAt(walls[what], pos.x, pos.y, to);
} }
} }
@ -3004,7 +3048,7 @@ void CBattleInterface::showAbsoluteObstacles(SDL_Surface * to)
blitAt(getObstacleImage(*oi), pos.x + oi->getInfo().width, pos.y + oi->getInfo().height, to); blitAt(getObstacleImage(*oi), pos.x + oi->getInfo().width, pos.y + oi->getInfo().height, to);
if (siegeH && siegeH->town->hasBuilt(BuildingID::CITADEL)) if (siegeH && siegeH->town->hasBuilt(BuildingID::CITADEL))
siegeH->printPartOfWall(to, 14); // show moat background siegeH->printPartOfWall(to, SiegeHelper::BACKGROUND_MOAT);
} }
void CBattleInterface::showHighlightedHexes(SDL_Surface * to) void CBattleInterface::showHighlightedHexes(SDL_Surface * to)
@ -3415,29 +3459,29 @@ BattleObjectsByHex CBattleInterface::sortObjectsByHex()
// Sort wall parts // Sort wall parts
if (siegeH) if (siegeH)
{ {
sorted.beforeAll.walls.push_back(1); // 1. background wall sorted.beforeAll.walls.push_back(SiegeHelper::BACKGROUND_WALL);
sorted.hex[135].walls.push_back(2); // 2. keep sorted.hex[135].walls.push_back(SiegeHelper::KEEP);
sorted.afterAll.walls.push_back(3); // 3. bottom tower sorted.afterAll.walls.push_back(SiegeHelper::BOTTOM_TOWER);
sorted.hex[182].walls.push_back(4); // 4. bottom wall sorted.hex[182].walls.push_back(SiegeHelper::BOTTOM_WALL);
sorted.hex[130].walls.push_back(5); // 5. wall below gate, sorted.hex[130].walls.push_back(SiegeHelper::WALL_BELLOW_GATE);
sorted.hex[62].walls.push_back(6); // 6. wall over gate sorted.hex[78].walls.push_back(SiegeHelper::WALL_OVER_GATE);
sorted.hex[12].walls.push_back(7); // 7. upper wall sorted.hex[12].walls.push_back(SiegeHelper::UPPER_WALL);
sorted.beforeAll.walls.push_back(8); // 8. upper tower sorted.beforeAll.walls.push_back(SiegeHelper::UPPER_TOWER);
//sorted.hex[94].walls.push_back(9); // 9. gate // Not implemented it seems sorted.hex[94].walls.push_back(SiegeHelper::GATE);
sorted.hex[112].walls.push_back(10); // 10. gate arch sorted.hex[112].walls.push_back(SiegeHelper::GATE_ARCH);
sorted.hex[165].walls.push_back(11); // 11. bottom static wall sorted.hex[165].walls.push_back(SiegeHelper::BOTTOM_STATIC_WALL);
sorted.hex[45].walls.push_back(12); // 12. upper static wall sorted.hex[45].walls.push_back(SiegeHelper::UPPER_STATIC_WALL);
if (siegeH && siegeH->town->hasBuilt(BuildingID::CITADEL)) if (siegeH && siegeH->town->hasBuilt(BuildingID::CITADEL))
{ {
sorted.beforeAll.walls.push_back(13); // 13. moat sorted.beforeAll.walls.push_back(SiegeHelper::MOAT);
//sorted.beforeAll.walls.push_back(14); // 14. mlip (moat background terrain), blit as absolute obstacle //sorted.beforeAll.walls.push_back(SiegeHelper::BACKGROUND_MOAT); // blit as absolute obstacle
sorted.hex[135].walls.push_back(15); // 15. keep turret cover sorted.hex[135].walls.push_back(SiegeHelper::KEEP_BATTLEMENT);
} }
if (siegeH && siegeH->town->hasBuilt(BuildingID::CASTLE)) if (siegeH && siegeH->town->hasBuilt(BuildingID::CASTLE))
{ {
sorted.afterAll.walls.push_back(16); // 16. lower turret cover sorted.afterAll.walls.push_back(SiegeHelper::BOTTOM_BATTLEMENT);
sorted.beforeAll.walls.push_back(17); // 17. upper turret cover sorted.beforeAll.walls.push_back(SiegeHelper::UPPER_BATTLEMENT);
} }
} }
return sorted; return sorted;

View File

@ -198,16 +198,32 @@ private:
SiegeHelper(const CGTownInstance * siegeTown, const CBattleInterface * _owner); //c-tor SiegeHelper(const CGTownInstance * siegeTown, const CBattleInterface * _owner); //c-tor
~SiegeHelper(); //d-tor ~SiegeHelper(); //d-tor
//filename getters
//what: 0 - background, 1 - background wall, 2 - keep, 3 - bottom tower, 4 - bottom wall,
// 5 - below gate, 6 - over gate, 7 - upper wall, 8 - upper tower, 9 - gate,
// 10 - gate arch, 11 - bottom static 12 - upper static, 13 - moat, 14 - moat background,
// 15 - keep battlement, 16 - bottom battlement, 17 - upper battlement;
// state uses EWallState enum
std::string getSiegeName(ui16 what) const; std::string getSiegeName(ui16 what) const;
std::string getSiegeName(ui16 what, int state) const; std::string getSiegeName(ui16 what, int state) const; // state uses EWallState enum
void printPartOfWall(SDL_Surface * to, int what);//what: 1 - background wall, 2 - keep, 3 - bottom tower, 4 - bottom wall, 5 - below gate, 6 - over gate, 7 - upper wall, 8 - uppert tower, 9 - gate, 10 - gate arch, 11 - bottom static wall, 12 - upper static wall, 15 - keep creature cover, 16 - bottom turret creature cover, 17 - upper turret creature cover void printPartOfWall(SDL_Surface * to, int what);
enum EWallVisual
{
BACKGROUND = 0,
BACKGROUND_WALL = 1,
KEEP,
BOTTOM_TOWER,
BOTTOM_WALL,
WALL_BELLOW_GATE,
WALL_OVER_GATE,
UPPER_WALL,
UPPER_TOWER,
GATE,
GATE_ARCH,
BOTTOM_STATIC_WALL,
UPPER_STATIC_WALL,
MOAT,
BACKGROUND_MOAT,
KEEP_BATTLEMENT,
BOTTOM_BATTLEMENT,
UPPER_BATTLEMENT
};
friend class CBattleInterface; friend class CBattleInterface;
} * siegeH; } * siegeH;
@ -341,6 +357,8 @@ public:
BattleHex fromWhichHexAttack(BattleHex myNumber); BattleHex fromWhichHexAttack(BattleHex myNumber);
void obstaclePlaced(const CObstacleInstance & oi); void obstaclePlaced(const CObstacleInstance & oi);
void gateStateChanged(const EGateState state);
const CGHeroInstance * currentHero() const; const CGHeroInstance * currentHero() const;
InfoAboutHero enemyHero() const; InfoAboutHero enemyHero() const;

View File

@ -327,6 +327,8 @@ BattleInfo * BattleInfo::setupBattle( int3 tile, ETerrainType terrain, BFieldTyp
//setting up siege obstacles //setting up siege obstacles
if (town && town->hasFort()) if (town && town->hasFort())
{ {
curB->si.gateState = EGateState::CLOSED;
for (int b = 0; b < curB->si.wallState.size(); ++b) for (int b = 0; b < curB->si.wallState.size(); ++b)
{ {
curB->si.wallState[b] = EWallState::INTACT; curB->si.wallState[b] = EWallState::INTACT;

View File

@ -33,6 +33,7 @@ class CRandomGenerator;
struct DLL_LINKAGE SiegeInfo struct DLL_LINKAGE SiegeInfo
{ {
std::array<si8, EWallPart::PARTS_COUNT> wallState; std::array<si8, EWallPart::PARTS_COUNT> wallState;
EGateState gateState;
// return EWallState decreased by value of damage points // return EWallState decreased by value of damage points
static EWallState::EWallState applyDamage(EWallState::EWallState state, unsigned int value) static EWallState::EWallState applyDamage(EWallState::EWallState state, unsigned int value)
@ -51,7 +52,7 @@ struct DLL_LINKAGE SiegeInfo
template <typename Handler> void serialize(Handler &h, const int version) template <typename Handler> void serialize(Handler &h, const int version)
{ {
h & wallState; h & wallState & gateState;
} }
}; };

View File

@ -62,13 +62,13 @@ namespace SiegeStuffThatShouldBeMovedToHandlers // <=== TODO
std::make_pair(183, EWallPart::BOTTOM_TOWER), std::make_pair(183, EWallPart::BOTTOM_TOWER),
std::make_pair(182, EWallPart::BOTTOM_WALL), std::make_pair(182, EWallPart::BOTTOM_WALL),
std::make_pair(130, EWallPart::BELOW_GATE), std::make_pair(130, EWallPart::BELOW_GATE),
std::make_pair(62, EWallPart::OVER_GATE), std::make_pair(78, EWallPart::OVER_GATE),
std::make_pair(29, EWallPart::UPPER_WALL), std::make_pair(29, EWallPart::UPPER_WALL),
std::make_pair(12, EWallPart::UPPER_TOWER), std::make_pair(12, EWallPart::UPPER_TOWER),
std::make_pair(95, EWallPart::INDESTRUCTIBLE_PART_OF_GATE), std::make_pair(95, EWallPart::INDESTRUCTIBLE_PART_OF_GATE),
std::make_pair(96, EWallPart::GATE), std::make_pair(96, EWallPart::GATE),
std::make_pair(45, EWallPart::INDESTRUCTIBLE_PART), std::make_pair(45, EWallPart::INDESTRUCTIBLE_PART),
std::make_pair(78, EWallPart::INDESTRUCTIBLE_PART), std::make_pair(62, EWallPart::INDESTRUCTIBLE_PART),
std::make_pair(112, EWallPart::INDESTRUCTIBLE_PART), std::make_pair(112, EWallPart::INDESTRUCTIBLE_PART),
std::make_pair(147, EWallPart::INDESTRUCTIBLE_PART) std::make_pair(147, EWallPart::INDESTRUCTIBLE_PART)
}; };
@ -442,6 +442,15 @@ si8 CBattleInfoEssentials::battleGetWallState(int partOfWall) const
return getBattle()->si.wallState[partOfWall]; return getBattle()->si.wallState[partOfWall];
} }
EGateState CBattleInfoEssentials::battleGetGateState() const
{
RETURN_IF_NOT_BATTLE(EGateState::NONE);
if(getBattle()->town == nullptr || getBattle()->town->fortLevel() == CGTownInstance::NONE)
return EGateState::NONE;
return getBattle()->si.gateState;
}
si8 CBattleInfoCallback::battleHasWallPenalty( const CStack * stack, BattleHex destHex ) const si8 CBattleInfoCallback::battleHasWallPenalty( const CStack * stack, BattleHex destHex ) const
{ {
return battleHasWallPenalty(stack, stack->position, destHex); return battleHasWallPenalty(stack, stack->position, destHex);
@ -1134,9 +1143,20 @@ AccessibilityInfo CBattleInfoCallback::getAccesibility() const
} }
//gate -> should be before stacks //gate -> should be before stacks
if(battleGetSiegeLevel() > 0 && battleGetWallState(EWallPart::GATE) != EWallState::DESTROYED) if(battleGetSiegeLevel() > 0)
{ {
ret[95] = ret[96] = EAccessibility::GATE; //block gate's hexes EAccessibility::EAccessibility accessability = EAccessibility::ACCESSIBLE;
switch(battleGetGateState())
{
case EGateState::CLOSED:
accessability = EAccessibility::GATE;
break;
case EGateState::BLOCKED:
accessability = EAccessibility::UNAVAILABLE;
break;
}
ret[ESiegeHex::GATE_OUTER] = ret[ESiegeHex::GATE_INNER] = accessability;
} }
//tiles occupied by standing stacks //tiles occupied by standing stacks
@ -1157,14 +1177,19 @@ AccessibilityInfo CBattleInfoCallback::getAccesibility() const
//walls //walls
if(battleGetSiegeLevel() > 0) if(battleGetSiegeLevel() > 0)
{ {
static const int permanentlyLocked[] = {12, 45, 78, 112, 147, 165}; static const int permanentlyLocked[] = {12, 45, 62, 112, 147, 165};
for(auto hex : permanentlyLocked) for(auto hex : permanentlyLocked)
ret[hex] = EAccessibility::UNAVAILABLE; ret[hex] = EAccessibility::UNAVAILABLE;
//TODO likely duplicated logic //TODO likely duplicated logic
static const std::pair<int, BattleHex> lockedIfNotDestroyed[] = //(which part of wall, which hex is blocked if this part of wall is not destroyed static const std::pair<int, BattleHex> lockedIfNotDestroyed[] =
{std::make_pair(2, BattleHex(182)), std::make_pair(3, BattleHex(130)), {
std::make_pair(4, BattleHex(62)), std::make_pair(5, BattleHex(29))}; //which part of wall, which hex is blocked if this part of wall is not destroyed
std::make_pair(2, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_4)),
std::make_pair(3, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_3)),
std::make_pair(4, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_2)),
std::make_pair(5, BattleHex(ESiegeHex::DESTRUCTIBLE_WALL_1))
};
for(auto & elem : lockedIfNotDestroyed) for(auto & elem : lockedIfNotDestroyed)
{ {

View File

@ -199,6 +199,7 @@ public:
// for determining state of a part of the wall; format: parameter [0] - keep, [1] - bottom tower, [2] - bottom wall, // for determining state of a part of the wall; format: parameter [0] - keep, [1] - bottom tower, [2] - bottom wall,
// [3] - below gate, [4] - over gate, [5] - upper wall, [6] - uppert tower, [7] - gate; returned value: 1 - intact, 2 - damaged, 3 - destroyed; 0 - no battle // [3] - below gate, [4] - over gate, [5] - upper wall, [6] - uppert tower, [7] - gate; returned value: 1 - intact, 2 - damaged, 3 - destroyed; 0 - no battle
si8 battleGetWallState(int partOfWall) const; si8 battleGetWallState(int partOfWall) const;
EGateState battleGetGateState() const;
//helpers //helpers
///returns all stacks, alive or dead or undead or mechanical :) ///returns all stacks, alive or dead or undead or mechanical :)

View File

@ -513,6 +513,29 @@ namespace EWallState
}; };
} }
enum class EGateState : ui8
{
NONE,
CLOSED,
BLOCKED, //dead or alive stack blocking from outside
OPENED,
DESTROYED
};
namespace ESiegeHex
{
enum ESiegeHex : si16
{
DESTRUCTIBLE_WALL_1 = 29,
DESTRUCTIBLE_WALL_2 = 78,
DESTRUCTIBLE_WALL_3 = 130,
DESTRUCTIBLE_WALL_4 = 182,
GATE_BRIDGE = 94,
GATE_OUTER = 95,
GATE_INNER = 96
};
}
namespace ETileType namespace ETileType
{ {
enum ETileType enum ETileType

View File

@ -69,6 +69,7 @@ public:
virtual void battleCatapultAttacked(const CatapultAttack & ca){}; //called when catapult makes an attack 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 battleStacksRemoved(const BattleStacksRemoved & bsr){}; //called when certain stack is completely removed from battlefield
virtual void battleObstaclePlaced(const CObstacleInstance &obstacle){}; virtual void battleObstaclePlaced(const CObstacleInstance &obstacle){};
virtual void battleGateStateChanged(const EGateState state){};
}; };
class DLL_LINKAGE IGameEventsReceiver class DLL_LINKAGE IGameEventsReceiver

View File

@ -1689,6 +1689,21 @@ struct BattleObstaclePlaced : public CPackForClient //3020
} }
}; };
struct BattleUpdateGateState : public CPackForClient//3021
{
BattleUpdateGateState(){type = 3021;};
void applyFirstCl(CClient *cl);
DLL_LINKAGE void applyGs(CGameState *gs);
EGateState state;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & state;
}
};
struct ShowInInfobox : public CPackForClient //107 struct ShowInInfobox : public CPackForClient //107
{ {

View File

@ -1225,6 +1225,12 @@ DLL_LINKAGE void BattleObstaclePlaced::applyGs( CGameState *gs )
gs->curB->obstacles.push_back(obstacle); gs->curB->obstacles.push_back(obstacle);
} }
DLL_LINKAGE void BattleUpdateGateState::applyGs(CGameState *gs)
{
if(gs->curB)
gs->curB->si.gateState = state;
}
void BattleResult::applyGs( CGameState *gs ) void BattleResult::applyGs( CGameState *gs )
{ {
for (CStack *s : gs->curB->stacks) for (CStack *s : gs->curB->stacks)

View File

@ -270,6 +270,7 @@ void registerTypesClientPacks2(Serializer &s)
s.template registerType<CPackForClient, SetStackEffect>(); s.template registerType<CPackForClient, SetStackEffect>();
s.template registerType<CPackForClient, BattleTriggerEffect>(); s.template registerType<CPackForClient, BattleTriggerEffect>();
s.template registerType<CPackForClient, BattleObstaclePlaced>(); s.template registerType<CPackForClient, BattleObstaclePlaced>();
s.template registerType<CPackForClient, BattleUpdateGateState>();
s.template registerType<CPackForClient, BattleSetStackProperty>(); s.template registerType<CPackForClient, BattleSetStackProperty>();
s.template registerType<CPackForClient, StacksInjured>(); s.template registerType<CPackForClient, StacksInjured>();
s.template registerType<CPackForClient, BattleResultsApplied>(); s.template registerType<CPackForClient, BattleResultsApplied>();

View File

@ -1005,7 +1005,8 @@ int CGameHandler::moveStack(int stack, BattleHex dest)
assert(gs->curB->isInTacticRange(dest)); assert(gs->curB->isInTacticRange(dest));
} }
if(curStack->position == dest) auto start = curStack->position;
if(start == dest)
return 0; return 0;
//initing necessary tables //initing necessary tables
@ -1032,16 +1033,60 @@ int CGameHandler::moveStack(int stack, BattleHex dest)
return 0; return 0;
} }
std::pair< std::vector<BattleHex>, int > path = gs->curB->getPath(curStack->position, dest, curStack); bool canUseGate = false;
auto dbState = gs->curB->si.gateState;
if(battleGetSiegeLevel() > 0 && !curStack->attackerOwned &&
dbState != EGateState::DESTROYED &&
dbState != EGateState::BLOCKED)
{
canUseGate = true;
}
std::pair< std::vector<BattleHex>, int > path = gs->curB->getPath(start, dest, curStack);
ret = path.second; ret = path.second;
int creSpeed = gs->curB->tacticDistance ? GameConstants::BFIELD_SIZE : curStack->Speed(); int creSpeed = gs->curB->tacticDistance ? GameConstants::BFIELD_SIZE : curStack->Speed();
auto isGateDrawbridgeHex = [&](BattleHex hex) -> bool
{
if(gs->curB->town->subID == ETownType::FORTRESS && hex == ESiegeHex::GATE_BRIDGE)
return true;
if(hex == ESiegeHex::GATE_OUTER)
return true;
if(hex == ESiegeHex::GATE_INNER)
return true;
return false;
};
auto occupyGateDrawbridgeHex = [&](BattleHex hex) -> bool
{
if(isGateDrawbridgeHex(hex))
return true;
if(curStack->doubleWide())
{
BattleHex otherHex = curStack->occupiedHex(hex);
if(otherHex.isValid() && isGateDrawbridgeHex(otherHex))
return true;
}
return false;
};
if(curStack->hasBonusOfType(Bonus::FLYING)) if(curStack->hasBonusOfType(Bonus::FLYING))
{ {
if(path.second <= creSpeed && path.first.size() > 0) if(path.second <= creSpeed && path.first.size() > 0)
{ {
if(canUseGate && dbState != EGateState::OPENED &&
occupyGateDrawbridgeHex(dest))
{
BattleUpdateGateState db;
db.state = EGateState::OPENED;
sendAndApply(&db);
}
//inform clients about move //inform clients about move
BattleStackMoved sm; BattleStackMoved sm;
sm.stack = curStack->ID; sm.stack = curStack->ID;
@ -1059,6 +1104,72 @@ int CGameHandler::moveStack(int stack, BattleHex dest)
std::vector<BattleHex> tiles; std::vector<BattleHex> tiles;
const int tilesToMove = std::max((int)(path.first.size() - creSpeed), 0); const int tilesToMove = std::max((int)(path.first.size() - creSpeed), 0);
int v = path.first.size()-1; int v = path.first.size()-1;
path.first.push_back(start);
// check if gate need to be open or closed at some point
BattleHex openGateAtHex, gateMayCloseAtHex;
if(canUseGate)
{
for(int i = path.first.size()-1; i >= 0; i--)
{
auto needOpenGates = [&](BattleHex hex) -> bool
{
if(gs->curB->town->subID == ETownType::FORTRESS && hex == ESiegeHex::GATE_BRIDGE)
return true;
if(hex == ESiegeHex::GATE_BRIDGE && i-1 >= 0 && path.first[i-1] == ESiegeHex::GATE_OUTER)
return true;
else if(hex == ESiegeHex::GATE_OUTER || hex == ESiegeHex::GATE_INNER)
return true;
return false;
};
auto hex = path.first[i];
if(!openGateAtHex.isValid() && dbState != EGateState::OPENED)
{
if(needOpenGates(hex))
openGateAtHex = path.first[i+1];
//TODO we need find batter way to handle double-wide stacks
//currently if only second occupied stack part is standing on gate / bridge hex then stack will start to wait for bridge to lower before it's needed. Though this is just a visual bug.
if(curStack->doubleWide())
{
BattleHex otherHex = curStack->occupiedHex(hex);
if(otherHex.isValid() && needOpenGates(otherHex))
openGateAtHex = path.first[i+2];
}
//gate may be opened and then closed during stack movement, but not other way around
if(openGateAtHex.isValid())
dbState = EGateState::OPENED;
}
if(!gateMayCloseAtHex.isValid() && dbState != EGateState::CLOSED)
{
if(hex == ESiegeHex::GATE_INNER && i-1 >= 0 && path.first[i-1] != ESiegeHex::GATE_OUTER)
{
gateMayCloseAtHex = path.first[i-1];
}
if(gs->curB->town->subID == ETownType::FORTRESS)
{
if(hex == ESiegeHex::GATE_BRIDGE && i-1 >= 0 && path.first[i-1] != ESiegeHex::GATE_OUTER)
{
gateMayCloseAtHex = path.first[i-1];
}
else if(hex == ESiegeHex::GATE_OUTER && i-1 >= 0 &&
path.first[i-1] != ESiegeHex::GATE_INNER &&
path.first[i-1] != ESiegeHex::GATE_BRIDGE)
{
gateMayCloseAtHex = path.first[i-1];
}
}
else if(hex == ESiegeHex::GATE_OUTER && i-1 >= 0 && path.first[i-1] != ESiegeHex::GATE_INNER)
{
gateMayCloseAtHex = path.first[i-1];
}
}
}
}
bool stackIsMoving = true; bool stackIsMoving = true;
@ -1070,11 +1181,23 @@ int CGameHandler::moveStack(int stack, BattleHex dest)
break; break;
} }
for(bool obstacleHit = false; (!obstacleHit) && (v >= tilesToMove); --v) bool gateStateChanging = false;
//special handling for opening gate on from starting hex
if(openGateAtHex.isValid() && openGateAtHex == start)
gateStateChanging = true;
else
{
for(bool obstacleHit = false; (!obstacleHit) && (!gateStateChanging) && (v >= tilesToMove); --v)
{ {
BattleHex hex = path.first[v]; BattleHex hex = path.first[v];
tiles.push_back(hex); tiles.push_back(hex);
if((openGateAtHex.isValid() && openGateAtHex == hex) ||
(gateMayCloseAtHex.isValid() && gateMayCloseAtHex == hex))
{
gateStateChanging = true;
}
//if we walked onto something, finalize this portion of stack movement check into obstacle //if we walked onto something, finalize this portion of stack movement check into obstacle
if((obstacle = battleGetObstacleOnPos(hex, false))) if((obstacle = battleGetObstacleOnPos(hex, false)))
obstacleHit = true; obstacleHit = true;
@ -1088,6 +1211,7 @@ int CGameHandler::moveStack(int stack, BattleHex dest)
obstacleHit = true; obstacleHit = true;
} }
} }
}
if(tiles.size() > 0) if(tiles.size() > 0)
{ {
@ -1121,6 +1245,26 @@ int CGameHandler::moveStack(int stack, BattleHex dest)
processObstacle(obstacle); processObstacle(obstacle);
if(curStack->alive()) if(curStack->alive())
processObstacle(obstacle2); processObstacle(obstacle2);
if(gateStateChanging)
{
if(curStack->position == openGateAtHex)
{
openGateAtHex = BattleHex();
//only open gate if stack is still alive
if(curStack->alive())
{
BattleUpdateGateState db;
db.state = EGateState::OPENED;
sendAndApply(&db);
}
}
else if(curStack->position == gateMayCloseAtHex)
{
gateMayCloseAtHex = BattleHex();
updateGateState();
}
}
} }
else else
//movement finished normally: we reached destination //movement finished normally: we reached destination
@ -1731,8 +1875,13 @@ void CGameHandler::setupBattle( int3 tile, const CArmedInstance *armies[2], cons
sendAndApply(&bs); sendAndApply(&bs);
} }
void CGameHandler::checkForBattleEnd() void CGameHandler::checkBattleStateChanges()
{ {
//check if drawbridge state need to be changes
if(battleGetSiegeLevel() > 0)
updateGateState();
//check if battle ended
if(auto result = battleIsFinished()) if(auto result = battleIsFinished())
{ {
setBattleResult(BattleResult::NORMAL, *result); setBattleResult(BattleResult::NORMAL, *result);
@ -3466,6 +3615,39 @@ bool CGameHandler::queryReply(QueryID qid, ui32 answer, PlayerColor player)
static EndAction end_action; static EndAction end_action;
void CGameHandler::updateGateState()
{
BattleUpdateGateState db;
db.state = gs->curB->si.gateState;
if(gs->curB->si.wallState[EWallPart::GATE] == EWallState::DESTROYED)
{
db.state = EGateState::DESTROYED;
}
else if(db.state == EGateState::OPENED)
{
if(!gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_OUTER), false) &&
!gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_INNER), false))
{
if(gs->curB->town->subID == ETownType::FORTRESS)
{
if(!gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_BRIDGE), false))
db.state = EGateState::CLOSED;
}
else if(gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_BRIDGE)))
db.state = EGateState::BLOCKED;
else
db.state = EGateState::CLOSED;
}
}
else if(gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_BRIDGE), false))
db.state = EGateState::BLOCKED;
else
db.state = EGateState::CLOSED;
if(db.state != gs->curB->si.gateState)
sendAndApply(&db);
}
bool CGameHandler::makeBattleAction( BattleAction &ba ) bool CGameHandler::makeBattleAction( BattleAction &ba )
{ {
bool ok = true; bool ok = true;
@ -4218,7 +4400,7 @@ bool CGameHandler::makeCustomAction( BattleAction &ba )
{ {
battleMadeAction.setn(true); battleMadeAction.setn(true);
} }
checkForBattleEnd(); checkBattleStateChanges();
if(battleResult.get()) if(battleResult.get())
{ {
battleMadeAction.setn(true); battleMadeAction.setn(true);
@ -5605,7 +5787,7 @@ void CGameHandler::runBattle()
break; break;
} }
//we're after action, all results applied //we're after action, all results applied
checkForBattleEnd(); //check if this action ended the battle checkBattleStateChanges(); //check if this action ended the battle
if(next != nullptr) if(next != nullptr)
{ {
@ -5657,7 +5839,7 @@ bool CGameHandler::makeAutomaticAction(const CStack *stack, BattleAction &ba)
sendAndApply(&bsa); sendAndApply(&bsa);
bool ret = makeBattleAction(ba); bool ret = makeBattleAction(ba);
checkForBattleEnd(); checkBattleStateChanges();
return ret; return ret;
} }

View File

@ -115,7 +115,7 @@ public:
void endBattle(int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2); //ends battle void endBattle(int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2); //ends battle
void prepareAttack(BattleAttack &bat, const CStack *att, const CStack *def, int distance, int targetHex); //distance - number of hexes travelled before attacking void prepareAttack(BattleAttack &bat, const CStack *att, const CStack *def, int distance, int targetHex); //distance - number of hexes travelled before attacking
void applyBattleEffects(BattleAttack &bat, const CStack *att, const CStack *def, int distance, bool secondary); //damage, drain life & fire shield void applyBattleEffects(BattleAttack &bat, const CStack *att, const CStack *def, int distance, bool secondary); //damage, drain life & fire shield
void checkForBattleEnd(); void checkBattleStateChanges();
void setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance *heroes[2], bool creatureBank, const CGTownInstance *town); void setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance *heroes[2], bool creatureBank, const CGTownInstance *town);
void setBattleResult(BattleResult::EResult resultType, int victoriusSide); void setBattleResult(BattleResult::EResult resultType, int victoriusSide);
void duelFinished(); void duelFinished();
@ -203,6 +203,7 @@ public:
PlayerColor getPlayerAt(CConnection *c) const; PlayerColor getPlayerAt(CConnection *c) const;
void playerMessage( PlayerColor player, const std::string &message, ObjectInstanceID currObj); void playerMessage( PlayerColor player, const std::string &message, ObjectInstanceID currObj);
void updateGateState();
bool makeBattleAction(BattleAction &ba); 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 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); bool makeCustomAction(BattleAction &ba);