| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374 | /* * 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 "../CGameInfo.h"#include "../CPlayerInterface.h"#include "../gui/CGuiHandler.h"#include "../media/ISoundPlayer.h"#include "../render/Canvas.h"#include "../render/IImage.h"#include "../render/IRenderHandler.h"#include "../../CCallback.h"#include "../../lib/CStack.h"#include "../../lib/entities/building/TownFortifications.h"#include "../../lib/mapObjects/CGTownInstance.h"#include "../../lib/networkPacks/PacksForClientBattle.h"ImagePath BattleSiegeController::getWallPieceImageName(EWallVisual::EWallVisual what, EWallState state) const{	auto getImageIndex = [&]() -> int	{		int health = static_cast<int>(state);		switch (what)		{			case EWallVisual::KEEP:			case EWallVisual::BOTTOM_TOWER:			case EWallVisual::UPPER_TOWER:				if (health > 0)					return 1;				else					return 2;			default:			{				int healthTotal = town->fortificationsLevel().wallsHealth;				if (healthTotal == health)					return 1;				if (health > 0)					return 2;				return 3;			}		};	};	const std::string & prefix = town->town->clientInfo.siegePrefix;	std::string addit = std::to_string(getImageIndex());	switch(what)	{	case EWallVisual::BACKGROUND_WALL:		{			auto faction = town->town->faction->getIndex();			if (faction == ETownType::RAMPART || faction == ETownType::NECROPOLIS || faction == ETownType::DUNGEON || faction == ETownType::STRONGHOLD)				return ImagePath::builtinTODO(prefix + "TPW1.BMP");			else				return ImagePath::builtinTODO(prefix + "TPWL.BMP");		}	case EWallVisual::KEEP:		return ImagePath::builtinTODO(prefix + "MAN" + addit + ".BMP");	case EWallVisual::BOTTOM_TOWER:		return ImagePath::builtinTODO(prefix + "TW1" + addit + ".BMP");	case EWallVisual::BOTTOM_WALL:		return ImagePath::builtinTODO(prefix + "WA1" + addit + ".BMP");	case EWallVisual::WALL_BELLOW_GATE:		return ImagePath::builtinTODO(prefix + "WA3" + addit + ".BMP");	case EWallVisual::WALL_OVER_GATE:		return ImagePath::builtinTODO(prefix + "WA4" + addit + ".BMP");	case EWallVisual::UPPER_WALL:		return ImagePath::builtinTODO(prefix + "WA6" + addit + ".BMP");	case EWallVisual::UPPER_TOWER:		return ImagePath::builtinTODO(prefix + "TW2" + addit + ".BMP");	case EWallVisual::GATE:		return ImagePath::builtinTODO(prefix + "DRW" + addit + ".BMP");	case EWallVisual::GATE_ARCH:		return ImagePath::builtinTODO(prefix + "ARCH.BMP");	case EWallVisual::BOTTOM_STATIC_WALL:		return ImagePath::builtinTODO(prefix + "WA2.BMP");	case EWallVisual::UPPER_STATIC_WALL:		return ImagePath::builtinTODO(prefix + "WA5.BMP");	case EWallVisual::MOAT:		return ImagePath::builtinTODO(prefix + "MOAT.BMP");	case EWallVisual::MOAT_BANK:		return ImagePath::builtinTODO(prefix + "MLIP.BMP");	case EWallVisual::KEEP_BATTLEMENT:		return ImagePath::builtinTODO(prefix + "MANC.BMP");	case EWallVisual::BOTTOM_BATTLEMENT:		return ImagePath::builtinTODO(prefix + "TW1C.BMP");	case EWallVisual::UPPER_BATTLEMENT:		return ImagePath::builtinTODO(prefix + "TW2C.BMP");	default:		return ImagePath();	}}void BattleSiegeController::showWallPiece(Canvas & canvas, EWallVisual::EWallVisual what){	auto & ci = town->town->clientInfo;	auto const & pos = ci.siegePositions[what];	if ( wallPieceImages[what] && pos.isValid())		canvas.draw(wallPieceImages[what], Point(pos.x, pos.y));}ImagePath BattleSiegeController::getBattleBackgroundName() const{	const std::string & prefix = town->town->clientInfo.siegePrefix;	return ImagePath::builtinTODO(prefix + "BACK.BMP");}bool BattleSiegeController::getWallPieceExistence(EWallVisual::EWallVisual what) const{	const auto & fortifications = town->fortificationsLevel();	switch (what)	{	case EWallVisual::MOAT:              return fortifications.hasMoat && town->town->clientInfo.siegePositions.at(EWallVisual::MOAT).isValid();	case EWallVisual::MOAT_BANK:         return fortifications.hasMoat && town->town->clientInfo.siegePositions.at(EWallVisual::MOAT_BANK).isValid();	case EWallVisual::KEEP_BATTLEMENT:   return fortifications.citadelHealth > 0 && owner.getBattle()->battleGetWallState(EWallPart::KEEP) != EWallState::DESTROYED;	case EWallVisual::UPPER_BATTLEMENT:  return fortifications.upperTowerHealth > 0 && owner.getBattle()->battleGetWallState(EWallPart::UPPER_TOWER) != EWallState::DESTROYED;	case EWallVisual::BOTTOM_BATTLEMENT: return fortifications.lowerTowerHealth > 0 && owner.getBattle()->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 ( !getWallPieceExistence(EWallVisual::EWallVisual(g)) )			continue;		wallPieceImages[g] = GH.renderHandler().loadImage(getWallPieceImageName(EWallVisual::EWallVisual(g), EWallState::REINFORCED), EImageBlitMode::COLORKEY);	}}const CCreature *BattleSiegeController::getTurretCreature(BattleHex position) const{	switch (position)	{		case BattleHex::CASTLE_CENTRAL_TOWER:			return town->fortificationsLevel().citadelShooter.toCreature();		case BattleHex::CASTLE_UPPER_TOWER:			return town->fortificationsLevel().upperTowerShooter.toCreature();		case BattleHex::CASTLE_BOTTOM_TOWER:			return town->fortificationsLevel().lowerTowerShooter.toCreature();	}	throw std::runtime_error("Unable to select shooter for tower at " + std::to_string(position.hex));}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.getBattle()->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] = GH.renderHandler().loadImage(getWallPieceImageName(EWallVisual::GATE,  stateId), EImageBlitMode::COLORKEY);	if (playSound)		CCS->soundh->playSound(soundBase::DRAWBRG);}void BattleSiegeController::showAbsoluteObstacles(Canvas & canvas){	if (getWallPieceExistence(EWallVisual::MOAT))		showWallPiece(canvas, EWallVisual::MOAT);	if (getWallPieceExistence(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.getBattle()->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 ( !getWallPieceExistence(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::OBSTACLES, 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.getBattle()->battleHexToWallPart(hex);	return owner.getBattle()->isWallPartAttackable(wallPart);}void BattleSiegeController::stackIsCatapulting(const CatapultAttack & ca){	if (ca.attacker != -1)	{		const CStack *stack = owner.getBattle()->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( AudioPath::builtin("WALLHIT") );		owner.stacksController->addNewAnim(new EffectAnimation(owner, AnimationPath::builtin("SGEXPL.DEF"), positions));	}	owner.waitForAnimations();	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.getBattle()->battleGetWallState(attackInfo.attackedPart));		wallPieceImages[wallId] = GH.renderHandler().loadImage(getWallPieceImageName(EWallVisual::EWallVisual(wallId), wallState), EImageBlitMode::COLORKEY);	}}const CGTownInstance *BattleSiegeController::getSiegedTown() const{	return town;}
 |