mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Merge pull request #181 from vcmi/feature/drawbridgeMechanics
Feature/drawbridge mechanics
This commit is contained in:
		| @@ -108,7 +108,7 @@ BattleAction CStupidAI::activeStack( const CStack * stack ) | ||||
| 	if(stack->type->idNumber == CreatureID::CATAPULT) | ||||
| 	{ | ||||
| 		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.actionType = Battle::CATAPULT; | ||||
|   | ||||
| @@ -11,6 +11,9 @@ GENERAL: | ||||
| * New cheat code: | ||||
| - vcmiglaurung - gives 5000 crystal dragons into each slot | ||||
|  | ||||
| BATTLES: | ||||
| * Drawbridge mechanics implemented (animation still missing) | ||||
|  | ||||
| ADVETURE AI: | ||||
| * Fixed AI trying to go through underground rock | ||||
| * Fixed several cases causing AI wandering aimlessly | ||||
|   | ||||
| @@ -1012,6 +1012,14 @@ void CPlayerInterface::battleObstaclePlaced(const CObstacleInstance &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) | ||||
| { | ||||
| 	THREAD_CREATED_BY_CLIENT; | ||||
|   | ||||
| @@ -220,6 +220,7 @@ public: | ||||
| 	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 battleGateStateChanged(const EGateState state) override; | ||||
| 	void yourTacticPhase(int distance) override; | ||||
|  | ||||
| 	//-------------// | ||||
|   | ||||
| @@ -658,6 +658,11 @@ void BattleObstaclePlaced::applyCl(CClient * cl) | ||||
| 	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 ) | ||||
| { | ||||
| 	BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(battleEnd,this); | ||||
|   | ||||
| @@ -1218,9 +1218,14 @@ void CBattleInterface::stackIsCatapulting(const CatapultAttack & ca) | ||||
|  | ||||
| 	for(auto attackInfo : ca.attackedParts) | ||||
| 	{ | ||||
| 		SDL_FreeSurface(siegeH->walls[attackInfo.attackedPart + 2]); | ||||
| 		siegeH->walls[attackInfo.attackedPart + 2] = BitmapHandler::loadBitmap( | ||||
| 			siegeH->getSiegeName(attackInfo.attackedPart + 2, curInt->cb->battleGetWallState(attackInfo.attackedPart))); | ||||
| 		int wallId = attackInfo.attackedPart + 2; | ||||
| 		//gate state changing handled separately | ||||
| 		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); | ||||
| } | ||||
|  | ||||
| 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 | ||||
| { | ||||
| 	if(attackingHeroInstance->tempOwner == curInt->playerID) | ||||
| @@ -2791,11 +2827,11 @@ void CBattleInterface::requestAutofightingAIToTakeAction() | ||||
| } | ||||
|  | ||||
| CBattleInterface::SiegeHelper::SiegeHelper(const CGTownInstance *siegeTown, const CBattleInterface * _owner) | ||||
|   : owner(_owner), town(siegeTown) | ||||
| 	: owner(_owner), town(siegeTown) | ||||
| { | ||||
| 	for(int g = 0; g < ARRAY_COUNT(walls); ++g) | ||||
| 	{ | ||||
| 		walls[g] = BitmapHandler::loadBitmap( getSiegeName(g) ); | ||||
| 		walls[g] = BitmapHandler::loadBitmap(getSiegeName(g)); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -2834,75 +2870,83 @@ std::string CBattleInterface::SiegeHelper::getSiegeName(ui16 what, int state) co | ||||
|  | ||||
| 	switch(what) | ||||
| 	{ | ||||
| 	case 0: //background | ||||
| 	case SiegeHelper::BACKGROUND: | ||||
| 		return prefix + "BACK.BMP"; | ||||
| 	case 1: //background wall | ||||
| 	case SiegeHelper::BACKGROUND_WALL: | ||||
| 		{ | ||||
| 			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"; | ||||
| 			default: | ||||
| 				return prefix + "TPWL.BMP"; | ||||
| 			} | ||||
| 		} | ||||
| 	case 2: //keep | ||||
| 	case SiegeHelper::KEEP: | ||||
| 		return prefix + "MAN" + addit + ".BMP"; | ||||
| 	case 3: //bottom tower | ||||
| 	case SiegeHelper::BOTTOM_TOWER: | ||||
| 		return prefix + "TW1" + addit + ".BMP"; | ||||
| 	case 4: //bottom wall | ||||
| 	case SiegeHelper::BOTTOM_WALL: | ||||
| 		return prefix + "WA1" + addit + ".BMP"; | ||||
| 	case 5: //below gate | ||||
| 	case SiegeHelper::WALL_BELLOW_GATE: | ||||
| 		return prefix + "WA3" + addit + ".BMP"; | ||||
| 	case 6: //over gate | ||||
| 	case SiegeHelper::WALL_OVER_GATE: | ||||
| 		return prefix + "WA4" + addit + ".BMP"; | ||||
| 	case 7: //upper wall | ||||
| 	case SiegeHelper::UPPER_WALL: | ||||
| 		return prefix + "WA6" + addit + ".BMP"; | ||||
| 	case 8: //upper tower | ||||
| 	case SiegeHelper::UPPER_TOWER: | ||||
| 		return prefix + "TW2" + addit + ".BMP"; | ||||
| 	case 9: //gate | ||||
| 	case SiegeHelper::GATE: | ||||
| 		return prefix + "DRW" + addit + ".BMP"; | ||||
| 	case 10: //gate arch | ||||
| 	case SiegeHelper::GATE_ARCH: | ||||
| 		return prefix + "ARCH.BMP"; | ||||
| 	case 11: //bottom static wall | ||||
| 	case SiegeHelper::BOTTOM_STATIC_WALL: | ||||
| 		return prefix + "WA2.BMP"; | ||||
| 	case 12: //upper static wall | ||||
| 	case SiegeHelper::UPPER_STATIC_WALL: | ||||
| 		return prefix + "WA5.BMP"; | ||||
| 	case 13: //moat | ||||
| 	case SiegeHelper::MOAT: | ||||
| 		return prefix + "MOAT.BMP"; | ||||
| 	case 14: //mlip | ||||
| 	case SiegeHelper::BACKGROUND_MOAT: | ||||
| 		return prefix + "MLIP.BMP"; | ||||
| 	case 15: //keep creature cover | ||||
| 	case SiegeHelper::KEEP_BATTLEMENT: | ||||
| 		return prefix + "MANC.BMP"; | ||||
| 	case 16: //bottom turret creature cover | ||||
| 	case SiegeHelper::BOTTOM_BATTLEMENT: | ||||
| 		return prefix + "TW1C.BMP"; | ||||
| 	case 17: //upper turret creature cover | ||||
| 	case SiegeHelper::UPPER_BATTLEMENT: | ||||
| 		return prefix + "TW2C.BMP"; | ||||
| 	default: | ||||
| 		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) | ||||
| { | ||||
| 	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.y = ci.siegePositions[what].y + owner->pos.y; | ||||
| 	} | ||||
|  | ||||
| 	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? | ||||
|  | ||||
| 	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); | ||||
| 	} | ||||
| } | ||||
| @@ -3004,7 +3048,7 @@ void CBattleInterface::showAbsoluteObstacles(SDL_Surface * to) | ||||
| 			blitAt(getObstacleImage(*oi), pos.x + oi->getInfo().width, pos.y + oi->getInfo().height, to); | ||||
|  | ||||
| 	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) | ||||
| @@ -3415,29 +3459,29 @@ BattleObjectsByHex CBattleInterface::sortObjectsByHex() | ||||
| 	// Sort wall parts | ||||
| 	if (siegeH) | ||||
| 	{ | ||||
| 		sorted.beforeAll.walls.push_back(1);  // 1. background wall | ||||
| 		sorted.hex[135].walls.push_back(2);   // 2. keep | ||||
| 		sorted.afterAll.walls.push_back(3);   // 3. bottom tower | ||||
| 		sorted.hex[182].walls.push_back(4);   // 4. bottom wall | ||||
| 		sorted.hex[130].walls.push_back(5);   // 5. wall below gate, | ||||
| 		sorted.hex[62].walls.push_back(6);    // 6. wall over gate | ||||
| 		sorted.hex[12].walls.push_back(7);    // 7. upper wall | ||||
| 		sorted.beforeAll.walls.push_back(8);  // 8. upper tower | ||||
| 		//sorted.hex[94].walls.push_back(9);  // 9. gate // Not implemented it seems | ||||
| 		sorted.hex[112].walls.push_back(10);  // 10. gate arch | ||||
| 		sorted.hex[165].walls.push_back(11);  // 11. bottom static wall | ||||
| 		sorted.hex[45].walls.push_back(12);   // 12. upper static wall | ||||
| 		sorted.beforeAll.walls.push_back(SiegeHelper::BACKGROUND_WALL); | ||||
| 		sorted.hex[135].walls.push_back(SiegeHelper::KEEP); | ||||
| 		sorted.afterAll.walls.push_back(SiegeHelper::BOTTOM_TOWER); | ||||
| 		sorted.hex[182].walls.push_back(SiegeHelper::BOTTOM_WALL); | ||||
| 		sorted.hex[130].walls.push_back(SiegeHelper::WALL_BELLOW_GATE); | ||||
| 		sorted.hex[78].walls.push_back(SiegeHelper::WALL_OVER_GATE); | ||||
| 		sorted.hex[12].walls.push_back(SiegeHelper::UPPER_WALL); | ||||
| 		sorted.beforeAll.walls.push_back(SiegeHelper::UPPER_TOWER); | ||||
| 		sorted.hex[94].walls.push_back(SiegeHelper::GATE); | ||||
| 		sorted.hex[112].walls.push_back(SiegeHelper::GATE_ARCH); | ||||
| 		sorted.hex[165].walls.push_back(SiegeHelper::BOTTOM_STATIC_WALL); | ||||
| 		sorted.hex[45].walls.push_back(SiegeHelper::UPPER_STATIC_WALL); | ||||
|  | ||||
| 		if (siegeH && siegeH->town->hasBuilt(BuildingID::CITADEL)) | ||||
| 		{ | ||||
| 			sorted.beforeAll.walls.push_back(13); // 13. moat | ||||
| 			//sorted.beforeAll.walls.push_back(14); // 14. mlip (moat background terrain), blit as absolute obstacle | ||||
| 			sorted.hex[135].walls.push_back(15);  // 15. keep turret cover | ||||
| 			sorted.beforeAll.walls.push_back(SiegeHelper::MOAT); | ||||
| 			//sorted.beforeAll.walls.push_back(SiegeHelper::BACKGROUND_MOAT); // blit as absolute obstacle | ||||
| 			sorted.hex[135].walls.push_back(SiegeHelper::KEEP_BATTLEMENT); | ||||
| 		} | ||||
| 		if (siegeH && siegeH->town->hasBuilt(BuildingID::CASTLE)) | ||||
| 		{ | ||||
| 			sorted.afterAll.walls.push_back(16);  // 16. lower turret cover | ||||
| 			sorted.beforeAll.walls.push_back(17); // 17. upper turret cover | ||||
| 			sorted.afterAll.walls.push_back(SiegeHelper::BOTTOM_BATTLEMENT); | ||||
| 			sorted.beforeAll.walls.push_back(SiegeHelper::UPPER_BATTLEMENT); | ||||
| 		} | ||||
| 	} | ||||
| 	return sorted; | ||||
|   | ||||
| @@ -148,7 +148,7 @@ private: | ||||
| 	void activateStack(); //sets activeStack to stackToActivate etc. //FIXME: No, it's not clear at all | ||||
| 	std::vector<BattleHex> occupyableHexes, //hexes available for active stack | ||||
| 		attackableHexes; //hexes attackable by active stack | ||||
|     bool stackCountOutsideHexes[GameConstants::BFIELD_SIZE]; // hexes that when in front of a unit cause it's amount box to move back | ||||
| 	bool stackCountOutsideHexes[GameConstants::BFIELD_SIZE]; // hexes that when in front of a unit cause it's amount box to move back | ||||
| 	BattleHex previouslyHoveredHex; //number of hex that was hovered by the cursor a while ago | ||||
| 	BattleHex currentlyHoveredHex; //number of hex that is supposed to be hovered (for a while it may be inappropriately set, but will be renewed soon) | ||||
| 	int attackingHex; //hex from which the stack would perform attack with current cursor | ||||
| @@ -198,16 +198,32 @@ private: | ||||
| 		SiegeHelper(const CGTownInstance * siegeTown, const CBattleInterface * _owner); //c-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, 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; | ||||
| 	} * siegeH; | ||||
| @@ -341,6 +357,8 @@ public: | ||||
| 	BattleHex fromWhichHexAttack(BattleHex myNumber); | ||||
| 	void obstaclePlaced(const CObstacleInstance & oi); | ||||
|  | ||||
| 	void gateStateChanged(const EGateState state); | ||||
|  | ||||
| 	const CGHeroInstance * currentHero() const; | ||||
| 	InfoAboutHero enemyHero() const; | ||||
|  | ||||
|   | ||||
| @@ -327,6 +327,8 @@ BattleInfo * BattleInfo::setupBattle( int3 tile, ETerrainType terrain, BFieldTyp | ||||
| 	//setting up siege obstacles | ||||
| 	if (town && town->hasFort()) | ||||
| 	{ | ||||
| 		curB->si.gateState = EGateState::CLOSED; | ||||
|  | ||||
| 		for (int b = 0; b < curB->si.wallState.size(); ++b) | ||||
| 		{ | ||||
| 			curB->si.wallState[b] = EWallState::INTACT; | ||||
|   | ||||
| @@ -33,6 +33,7 @@ class CRandomGenerator; | ||||
| struct DLL_LINKAGE SiegeInfo | ||||
| { | ||||
| 	std::array<si8, EWallPart::PARTS_COUNT> wallState; | ||||
| 	EGateState gateState; | ||||
|  | ||||
| 	// return EWallState decreased by value of damage points | ||||
| 	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) | ||||
| 	{ | ||||
| 		h & wallState; | ||||
| 		h & wallState & gateState; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -62,13 +62,13 @@ namespace SiegeStuffThatShouldBeMovedToHandlers //  <=== TODO | ||||
| 		std::make_pair(183, EWallPart::BOTTOM_TOWER), | ||||
| 		std::make_pair(182, EWallPart::BOTTOM_WALL), | ||||
| 		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(12,  EWallPart::UPPER_TOWER), | ||||
| 		std::make_pair(95,  EWallPart::INDESTRUCTIBLE_PART_OF_GATE), | ||||
| 		std::make_pair(96,  EWallPart::GATE), | ||||
| 		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(147, EWallPart::INDESTRUCTIBLE_PART) | ||||
| 	}; | ||||
| @@ -442,6 +442,15 @@ si8 CBattleInfoEssentials::battleGetWallState(int partOfWall) const | ||||
| 	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 | ||||
| { | ||||
| 	return battleHasWallPenalty(stack, stack->position, destHex); | ||||
| @@ -1134,9 +1143,20 @@ AccessibilityInfo CBattleInfoCallback::getAccesibility() const | ||||
| 	} | ||||
|  | ||||
| 	//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 | ||||
| @@ -1157,14 +1177,19 @@ AccessibilityInfo CBattleInfoCallback::getAccesibility() const | ||||
| 	//walls | ||||
| 	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) | ||||
| 			ret[hex] = EAccessibility::UNAVAILABLE; | ||||
|  | ||||
| 		//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 | ||||
| 			{std::make_pair(2, BattleHex(182)), std::make_pair(3, BattleHex(130)), | ||||
| 			std::make_pair(4, BattleHex(62)), std::make_pair(5, BattleHex(29))}; | ||||
| 		static const std::pair<int, BattleHex> lockedIfNotDestroyed[] = | ||||
| 		{ | ||||
| 			//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) | ||||
| 		{ | ||||
|   | ||||
| @@ -199,6 +199,7 @@ public: | ||||
| 	// 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 | ||||
| 	si8 battleGetWallState(int partOfWall) const; | ||||
| 	EGateState battleGetGateState() const; | ||||
|  | ||||
| 	//helpers | ||||
| 	///returns all stacks, alive or dead or undead or mechanical :) | ||||
|   | ||||
| @@ -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 | ||||
| { | ||||
| 	enum ETileType | ||||
|   | ||||
| @@ -69,6 +69,7 @@ public: | ||||
| 	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){}; | ||||
| 	virtual void battleGateStateChanged(const EGateState state){}; | ||||
| }; | ||||
|  | ||||
| class DLL_LINKAGE IGameEventsReceiver | ||||
|   | ||||
| @@ -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 | ||||
| { | ||||
|   | ||||
| @@ -1225,6 +1225,12 @@ DLL_LINKAGE void BattleObstaclePlaced::applyGs( CGameState *gs ) | ||||
| 	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 ) | ||||
| { | ||||
| 	for (CStack *s : gs->curB->stacks) | ||||
|   | ||||
| @@ -270,6 +270,7 @@ void registerTypesClientPacks2(Serializer &s) | ||||
| 	s.template registerType<CPackForClient, SetStackEffect>(); | ||||
| 	s.template registerType<CPackForClient, BattleTriggerEffect>(); | ||||
| 	s.template registerType<CPackForClient, BattleObstaclePlaced>(); | ||||
| 	s.template registerType<CPackForClient, BattleUpdateGateState>(); | ||||
| 	s.template registerType<CPackForClient, BattleSetStackProperty>(); | ||||
| 	s.template registerType<CPackForClient, StacksInjured>(); | ||||
| 	s.template registerType<CPackForClient, BattleResultsApplied>(); | ||||
|   | ||||
| @@ -1005,7 +1005,8 @@ int CGameHandler::moveStack(int stack, BattleHex dest) | ||||
| 		assert(gs->curB->isInTacticRange(dest)); | ||||
| 	} | ||||
|  | ||||
| 	if(curStack->position == dest) | ||||
| 	auto start = curStack->position; | ||||
| 	if(start == dest) | ||||
| 		return 0; | ||||
|  | ||||
| 	//initing necessary tables | ||||
| @@ -1032,16 +1033,60 @@ int CGameHandler::moveStack(int stack, BattleHex dest) | ||||
| 		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; | ||||
|  | ||||
| 	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(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 | ||||
| 			BattleStackMoved sm; | ||||
| 			sm.stack = curStack->ID; | ||||
| @@ -1059,6 +1104,72 @@ int CGameHandler::moveStack(int stack, BattleHex dest) | ||||
| 		std::vector<BattleHex> tiles; | ||||
| 		const int tilesToMove = std::max((int)(path.first.size() - creSpeed), 0); | ||||
| 		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; | ||||
|  | ||||
| @@ -1070,22 +1181,35 @@ int CGameHandler::moveStack(int stack, BattleHex dest) | ||||
| 				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 | ||||
| 			{ | ||||
| 				BattleHex hex = path.first[v]; | ||||
| 				tiles.push_back(hex); | ||||
|  | ||||
| 				//if we walked onto something, finalize this portion of stack movement check into obstacle | ||||
| 				if((obstacle = battleGetObstacleOnPos(hex, false))) | ||||
| 					obstacleHit = true; | ||||
|  | ||||
| 				if(curStack->doubleWide()) | ||||
| 				for(bool obstacleHit = false; (!obstacleHit) && (!gateStateChanging) && (v >= tilesToMove); --v) | ||||
| 				{ | ||||
| 					BattleHex otherHex = curStack->occupiedHex(hex); | ||||
| 					BattleHex hex = path.first[v]; | ||||
| 					tiles.push_back(hex); | ||||
|  | ||||
| 					//two hex creature hit obstacle by backside | ||||
| 					if(otherHex.isValid() && ((obstacle2 = battleGetObstacleOnPos(otherHex, false)))) | ||||
| 					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((obstacle = battleGetObstacleOnPos(hex, false))) | ||||
| 						obstacleHit = true; | ||||
|  | ||||
| 					if(curStack->doubleWide()) | ||||
| 					{ | ||||
| 						BattleHex otherHex = curStack->occupiedHex(hex); | ||||
|  | ||||
| 						//two hex creature hit obstacle by backside | ||||
| 						if(otherHex.isValid() && ((obstacle2 = battleGetObstacleOnPos(otherHex, false)))) | ||||
| 							obstacleHit = true; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| @@ -1121,6 +1245,26 @@ int CGameHandler::moveStack(int stack, BattleHex dest) | ||||
| 				processObstacle(obstacle); | ||||
| 				if(curStack->alive()) | ||||
| 					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 | ||||
| 				//movement finished normally: we reached destination | ||||
| @@ -1731,8 +1875,13 @@ void CGameHandler::setupBattle( int3 tile, const CArmedInstance *armies[2], cons | ||||
| 	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()) | ||||
| 	{ | ||||
| 		setBattleResult(BattleResult::NORMAL, *result); | ||||
| @@ -3466,6 +3615,39 @@ bool CGameHandler::queryReply(QueryID qid, ui32 answer, PlayerColor player) | ||||
|  | ||||
| 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 ok = true; | ||||
| @@ -4218,7 +4400,7 @@ bool CGameHandler::makeCustomAction( BattleAction &ba ) | ||||
| 			{ | ||||
| 				battleMadeAction.setn(true); | ||||
| 			} | ||||
| 			checkForBattleEnd(); | ||||
| 			checkBattleStateChanges(); | ||||
| 			if(battleResult.get()) | ||||
| 			{ | ||||
| 				battleMadeAction.setn(true); | ||||
| @@ -5605,7 +5787,7 @@ void CGameHandler::runBattle() | ||||
| 					break; | ||||
| 				} | ||||
| 				//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) | ||||
| 				{ | ||||
| @@ -5657,7 +5839,7 @@ bool CGameHandler::makeAutomaticAction(const CStack *stack, BattleAction &ba) | ||||
| 	sendAndApply(&bsa); | ||||
|  | ||||
| 	bool ret = makeBattleAction(ba); | ||||
| 	checkForBattleEnd(); | ||||
| 	checkBattleStateChanges(); | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -115,7 +115,7 @@ public: | ||||
| 	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 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 setBattleResult(BattleResult::EResult resultType, int victoriusSide); | ||||
| 	void duelFinished(); | ||||
| @@ -203,6 +203,7 @@ public: | ||||
| 	PlayerColor getPlayerAt(CConnection *c) const; | ||||
|  | ||||
| 	void playerMessage( PlayerColor player, const std::string &message, ObjectInstanceID currObj); | ||||
| 	void updateGateState(); | ||||
| 	bool makeBattleAction(BattleAction &ba); | ||||
| 	bool makeAutomaticAction(const CStack *stack, BattleAction &ba); //used when action is taken by stack without volition of player (eg. unguided catapult attack) | ||||
| 	bool makeCustomAction(BattleAction &ba); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user