| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378 | /* * BattleSiegeController.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * * License: GNU General Public License v2.0 or later * Full text of license available in license.txt file, in main folder * */#include "StdInc.h"#include "BattleSiegeController.h"#include "BattleAnimationClasses.h"#include "BattleInterface.h"#include "BattleInterfaceClasses.h"#include "BattleStacksController.h"#include "BattleFieldController.h"#include "BattleRenderer.h"#include "../CMusicHandler.h"#include "../CGameInfo.h"#include "../CPlayerInterface.h"#include "../gui/CAnimation.h"#include "../gui/Canvas.h"#include "../../CCallback.h"#include "../../lib/NetPacks.h"#include "../../lib/CStack.h"#include "../../lib/mapObjects/CGTownInstance.h"std::string BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisual what, EWallState state) const{	auto getImageIndex = [&]() -> int	{		bool isTower = (what == EWallVisual::KEEP || what == EWallVisual::BOTTOM_TOWER || what == EWallVisual::UPPER_TOWER);		switch (state)		{		case EWallState::REINFORCED :			return 1;		case EWallState::INTACT :			if (town->hasBuilt(BuildingID::CASTLE))				return 2; // reinforced walls were damaged			else				return 1;		case EWallState::DAMAGED :			// towers don't have separate image here - INTACT and DAMAGED is 1, DESTROYED is 2			if (isTower)				return 1;			else				return 2;		case EWallState::DESTROYED :			if (isTower)				return 2;			else				return 3;		}		return 1;	};	const std::string & prefix = town->town->clientInfo.siegePrefix;	std::string addit = boost::lexical_cast<std::string>(getImageIndex());	switch(what)	{	case EWallVisual::BACKGROUND_WALL:		{			switch(town->town->faction->index)			{			case ETownType::RAMPART:			case ETownType::NECROPOLIS:			case ETownType::DUNGEON:			case ETownType::STRONGHOLD:				return prefix + "TPW1.BMP";			default:				return prefix + "TPWL.BMP";			}		}	case EWallVisual::KEEP:		return prefix + "MAN" + addit + ".BMP";	case EWallVisual::BOTTOM_TOWER:		return prefix + "TW1" + addit + ".BMP";	case EWallVisual::BOTTOM_WALL:		return prefix + "WA1" + addit + ".BMP";	case EWallVisual::WALL_BELLOW_GATE:		return prefix + "WA3" + addit + ".BMP";	case EWallVisual::WALL_OVER_GATE:		return prefix + "WA4" + addit + ".BMP";	case EWallVisual::UPPER_WALL:		return prefix + "WA6" + addit + ".BMP";	case EWallVisual::UPPER_TOWER:		return prefix + "TW2" + addit + ".BMP";	case EWallVisual::GATE:		return prefix + "DRW" + addit + ".BMP";	case EWallVisual::GATE_ARCH:		return prefix + "ARCH.BMP";	case EWallVisual::BOTTOM_STATIC_WALL:		return prefix + "WA2.BMP";	case EWallVisual::UPPER_STATIC_WALL:		return prefix + "WA5.BMP";	case EWallVisual::MOAT:		return prefix + "MOAT.BMP";	case EWallVisual::MOAT_BANK:		return prefix + "MLIP.BMP";	case EWallVisual::KEEP_BATTLEMENT:		return prefix + "MANC.BMP";	case EWallVisual::BOTTOM_BATTLEMENT:		return prefix + "TW1C.BMP";	case EWallVisual::UPPER_BATTLEMENT:		return prefix + "TW2C.BMP";	default:		return "";	}}void BattleSiegeController::showWallPiece(Canvas & canvas, EWallVisual::EWallVisual what){	auto & ci = town->town->clientInfo;	auto const & pos = ci.siegePositions[what];	if ( wallPieceImages[what])		canvas.draw(wallPieceImages[what], Point(pos.x, pos.y));}std::string BattleSiegeController::getBattleBackgroundName() const{	const std::string & prefix = town->town->clientInfo.siegePrefix;	return prefix + "BACK.BMP";}bool BattleSiegeController::getWallPieceExistance(EWallVisual::EWallVisual what) const{	//FIXME: use this instead of buildings test?	//ui8 siegeLevel = owner.curInt->cb->battleGetSiegeLevel();	switch (what)	{	case EWallVisual::MOAT:              return town->hasBuilt(BuildingID::CITADEL) && town->town->faction->index != ETownType::TOWER;	case EWallVisual::MOAT_BANK:         return town->hasBuilt(BuildingID::CITADEL) && town->town->faction->index != ETownType::TOWER && town->town->faction->index != ETownType::NECROPOLIS;	case EWallVisual::KEEP_BATTLEMENT:   return town->hasBuilt(BuildingID::CITADEL) && owner.curInt->cb->battleGetWallState(EWallPart::KEEP) != EWallState::DESTROYED;	case EWallVisual::UPPER_BATTLEMENT:  return town->hasBuilt(BuildingID::CASTLE) && owner.curInt->cb->battleGetWallState(EWallPart::UPPER_TOWER) != EWallState::DESTROYED;	case EWallVisual::BOTTOM_BATTLEMENT: return town->hasBuilt(BuildingID::CASTLE) && owner.curInt->cb->battleGetWallState(EWallPart::BOTTOM_TOWER) != EWallState::DESTROYED;	default:                             return true;	}}BattleHex BattleSiegeController::getWallPiecePosition(EWallVisual::EWallVisual what) const{	static const std::array<BattleHex, 18> wallsPositions = {		BattleHex::INVALID,        // BACKGROUND,         // handled separately		BattleHex::HEX_BEFORE_ALL, // BACKGROUND_WALL,		135,                       // KEEP,		BattleHex::HEX_AFTER_ALL,  // BOTTOM_TOWER,		182,                       // BOTTOM_WALL,		130,                       // WALL_BELLOW_GATE,		62,                        // WALL_OVER_GATE,		12,                        // UPPER_WALL,		BattleHex::HEX_BEFORE_ALL, // UPPER_TOWER,		BattleHex::HEX_BEFORE_ALL, // GATE,               // 94		112,                       // GATE_ARCH,		165,                       // BOTTOM_STATIC_WALL,		45,                        // UPPER_STATIC_WALL,		BattleHex::INVALID,        // MOAT,               // printed as absolute obstacle		BattleHex::INVALID,        // MOAT_BANK,          // printed as absolute obstacle		135,                       // KEEP_BATTLEMENT,		BattleHex::HEX_AFTER_ALL,  // BOTTOM_BATTLEMENT,		BattleHex::HEX_BEFORE_ALL, // UPPER_BATTLEMENT,	};	return wallsPositions[what];}BattleSiegeController::BattleSiegeController(BattleInterface & owner, const CGTownInstance *siegeTown):	owner(owner),	town(siegeTown){	assert(owner.fieldController.get() == nullptr); // must be created after this	for (int g = 0; g < wallPieceImages.size(); ++g)	{		if ( g == EWallVisual::GATE ) // gate is initially closed and has no image to display in this state			continue;		if ( !getWallPieceExistance(EWallVisual::EWallVisual(g)) )			continue;		wallPieceImages[g] = IImage::createFromFile(getWallPieceImageName(EWallVisual::EWallVisual(g), EWallState::REINFORCED));	}}const CCreature *BattleSiegeController::getTurretCreature() const{	return CGI->creh->objects[town->town->clientInfo.siegeShooter];}Point BattleSiegeController::getTurretCreaturePosition( BattleHex position ) const{	// Turret positions are read out of the config/wall_pos.txt	int posID = 0;	switch (position)	{	case BattleHex::CASTLE_CENTRAL_TOWER: // keep creature		posID = EWallVisual::CREATURE_KEEP;		break;	case BattleHex::CASTLE_BOTTOM_TOWER: // bottom creature		posID = EWallVisual::CREATURE_BOTTOM_TOWER;		break;	case BattleHex::CASTLE_UPPER_TOWER: // upper creature		posID = EWallVisual::CREATURE_UPPER_TOWER;		break;	}	if (posID != 0)	{		return {			town->town->clientInfo.siegePositions[posID].x,			town->town->clientInfo.siegePositions[posID].y		};	}	assert(0);	return Point(0,0);}void BattleSiegeController::gateStateChanged(const EGateState state){	auto oldState = owner.curInt->cb->battleGetGateState();	bool playSound = false;	auto 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;	}	if (oldState != EGateState::NONE && oldState != EGateState::CLOSED && oldState != EGateState::BLOCKED)		wallPieceImages[EWallVisual::GATE] = nullptr;	if (stateId != EWallState::NONE)		wallPieceImages[EWallVisual::GATE] = IImage::createFromFile(getWallPieceImageName(EWallVisual::GATE,  stateId));	if (playSound)		CCS->soundh->playSound(soundBase::DRAWBRG);}void BattleSiegeController::showAbsoluteObstacles(Canvas & canvas){	if (getWallPieceExistance(EWallVisual::MOAT))		showWallPiece(canvas, EWallVisual::MOAT);	if (getWallPieceExistance(EWallVisual::MOAT_BANK))		showWallPiece(canvas, EWallVisual::MOAT_BANK);}BattleHex BattleSiegeController::getTurretBattleHex(EWallVisual::EWallVisual wallPiece) const{	switch(wallPiece)	{	case EWallVisual::KEEP_BATTLEMENT:   return BattleHex::CASTLE_CENTRAL_TOWER;	case EWallVisual::BOTTOM_BATTLEMENT: return BattleHex::CASTLE_BOTTOM_TOWER;	case EWallVisual::UPPER_BATTLEMENT:  return BattleHex::CASTLE_UPPER_TOWER;	}	assert(0);	return BattleHex::INVALID;}const CStack * BattleSiegeController::getTurretStack(EWallVisual::EWallVisual wallPiece) const{	for (auto & stack : owner.curInt->cb->battleGetAllStacks(true))	{		if ( stack->initialPosition == getTurretBattleHex(wallPiece))			return stack;	}	assert(0);	return nullptr;}void BattleSiegeController::collectRenderableObjects(BattleRenderer & renderer){	for (int i = EWallVisual::WALL_FIRST; i <= EWallVisual::WALL_LAST; ++i)	{		auto wallPiece = EWallVisual::EWallVisual(i);		if ( !getWallPieceExistance(wallPiece))			continue;		if ( getWallPiecePosition(wallPiece) == BattleHex::INVALID)			continue;		if (wallPiece == EWallVisual::KEEP_BATTLEMENT ||			wallPiece == EWallVisual::BOTTOM_BATTLEMENT ||			wallPiece == EWallVisual::UPPER_BATTLEMENT)		{			renderer.insert( EBattleFieldLayer::STACKS, getWallPiecePosition(wallPiece), [this, wallPiece](BattleRenderer::RendererRef canvas){				owner.stacksController->showStack(canvas, getTurretStack(wallPiece));			});			renderer.insert( EBattleFieldLayer::BATTLEMENTS, getWallPiecePosition(wallPiece), [this, wallPiece](BattleRenderer::RendererRef canvas){				showWallPiece(canvas, wallPiece);			});		}		renderer.insert( EBattleFieldLayer::WALLS, getWallPiecePosition(wallPiece), [this, wallPiece](BattleRenderer::RendererRef canvas){			showWallPiece(canvas, wallPiece);		});	}}bool BattleSiegeController::isAttackableByCatapult(BattleHex hex) const{	if (owner.tacticsMode)		return false;	auto wallPart = owner.curInt->cb->battleHexToWallPart(hex);	if (!owner.curInt->cb->isWallPartPotentiallyAttackable(wallPart))		return false;	auto state = owner.curInt->cb->battleGetWallState(wallPart);	return state != EWallState::DESTROYED && state != EWallState::NONE;}void BattleSiegeController::stackIsCatapulting(const CatapultAttack & ca){	assert(owner.getAnimationCondition(EAnimationEvents::ACTION) == false);	if (ca.attacker != -1)	{		const CStack *stack = owner.curInt->cb->battleGetStackByID(ca.attacker);		for (auto attackInfo : ca.attackedParts)		{			owner.stacksController->addNewAnim(new CatapultAnimation(owner, stack, attackInfo.destinationTile, nullptr, attackInfo.damageDealt));		}	}	else	{		std::vector<Point> positions;		//no attacker stack, assume spell-related (earthquake) - only hit animation		for (auto attackInfo : ca.attackedParts)			positions.push_back(owner.stacksController->getStackPositionAtHex(attackInfo.destinationTile, nullptr) + Point(99, 120));		CCS->soundh->playSound( "WALLHIT" );		owner.stacksController->addNewAnim(new EffectAnimation(owner, "SGEXPL.DEF", positions));	}	owner.waitForAnimationCondition(EAnimationEvents::ACTION, false);	owner.setAnimationCondition(EAnimationEvents::HIT, false);	for (auto attackInfo : ca.attackedParts)	{		int wallId = static_cast<int>(attackInfo.attackedPart) + EWallVisual::DESTRUCTIBLE_FIRST;		//gate state changing handled separately		if (wallId == EWallVisual::GATE)			continue;		auto wallState = EWallState(owner.curInt->cb->battleGetWallState(attackInfo.attackedPart));		wallPieceImages[wallId] = IImage::createFromFile(getWallPieceImageName(EWallVisual::EWallVisual(wallId), wallState));	}}const CGTownInstance *BattleSiegeController::getSiegedTown() const{	return town;}
 |