mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Merge pull request #1932 from rilian-la-te/proper-teleport
VCMI: teleport redesign
This commit is contained in:
		| @@ -569,10 +569,10 @@ bool BattleActionsController::actionIsLegal(PossiblePlayerBattleAction action, B | ||||
| 			return false; | ||||
|  | ||||
| 		case PossiblePlayerBattleAction::ANY_LOCATION: | ||||
| 			return isCastingPossibleHere(action.spell().toSpell(), owner.stacksController->getActiveStack(), targetStack, targetHex); | ||||
| 			return isCastingPossibleHere(action.spell().toSpell(), targetStack, targetHex); | ||||
|  | ||||
| 		case PossiblePlayerBattleAction::AIMED_SPELL_CREATURE: | ||||
| 			return !selectedStack && targetStack && isCastingPossibleHere(action.spell().toSpell(), owner.stacksController->getActiveStack(), targetStack, targetHex); | ||||
| 			return !selectedStack && targetStack && isCastingPossibleHere(action.spell().toSpell(), targetStack, targetHex); | ||||
|  | ||||
| 		case PossiblePlayerBattleAction::RANDOM_GENIE_SPELL: | ||||
| 			if(targetStack && targetStackOwned && targetStack != owner.stacksController->getActiveStack() && targetStack->alive()) //only positive spells for other allied creatures | ||||
| @@ -583,18 +583,14 @@ bool BattleActionsController::actionIsLegal(PossiblePlayerBattleAction action, B | ||||
| 			return false; | ||||
|  | ||||
| 		case PossiblePlayerBattleAction::TELEPORT: | ||||
| 		{ | ||||
| 			ui8 skill = getCurrentSpellcaster()->getEffectLevel(SpellID(SpellID::TELEPORT).toSpell()); | ||||
| 			return owner.curInt->cb->battleCanTeleportTo(selectedStack, targetHex, skill); | ||||
| 		} | ||||
| 			return selectedStack && isCastingPossibleHere(action.spell().toSpell(), selectedStack, targetHex); | ||||
|  | ||||
| 		case PossiblePlayerBattleAction::SACRIFICE: //choose our living stack to sacrifice | ||||
| 			return targetStack && targetStack != selectedStack && targetStackOwned && targetStack->alive(); | ||||
|  | ||||
| 		case PossiblePlayerBattleAction::OBSTACLE: | ||||
| 		case PossiblePlayerBattleAction::FREE_LOCATION: | ||||
| 			return isCastingPossibleHere(action.spell().toSpell(), owner.stacksController->getActiveStack(), targetStack, targetHex); | ||||
| 			return isCastingPossibleHere(action.spell().toSpell(), owner.stacksController->getActiveStack(), targetStack, targetHex); | ||||
| 			return isCastingPossibleHere(action.spell().toSpell(), targetStack, targetHex); | ||||
|  | ||||
| 		case PossiblePlayerBattleAction::CATAPULT: | ||||
| 			return owner.siegeController && owner.siegeController->isAttackableByCatapult(targetHex); | ||||
| @@ -904,7 +900,7 @@ spells::Mode BattleActionsController::getCurrentCastMode() const | ||||
|  | ||||
| } | ||||
|  | ||||
| bool BattleActionsController::isCastingPossibleHere(const CSpell * currentSpell, const CStack *casterStack, const CStack *targetStack, BattleHex targetHex) | ||||
| bool BattleActionsController::isCastingPossibleHere(const CSpell * currentSpell, const CStack *targetStack, BattleHex targetHex) | ||||
| { | ||||
| 	assert(currentSpell); | ||||
| 	if (!currentSpell) | ||||
| @@ -915,6 +911,8 @@ bool BattleActionsController::isCastingPossibleHere(const CSpell * currentSpell, | ||||
| 	const spells::Mode mode = heroSpellToCast ? spells::Mode::HERO : spells::Mode::CREATURE_ACTIVE; | ||||
|  | ||||
| 	spells::Target target; | ||||
| 	if(targetStack) | ||||
| 		target.emplace_back(targetStack); | ||||
| 	target.emplace_back(targetHex); | ||||
|  | ||||
| 	spells::BattleCast cast(owner.curInt->cb.get(), caster, mode, currentSpell); | ||||
|   | ||||
| @@ -53,7 +53,7 @@ class BattleActionsController | ||||
| 	/// stack that has been selected as first target for multi-target spells (Teleport & Sacrifice) | ||||
| 	const CStack * selectedStack; | ||||
|  | ||||
| 	bool isCastingPossibleHere (const CSpell * spell, const CStack *sactive, const CStack *shere, BattleHex myNumber); | ||||
| 	bool isCastingPossibleHere (const CSpell * spell, const CStack *shere, BattleHex myNumber); | ||||
| 	bool canStackMoveHere (const CStack *sactive, BattleHex MyNumber) const; //TODO: move to BattleState / callback | ||||
| 	std::vector<PossiblePlayerBattleAction> getPossibleActionsForStack (const CStack *stack) const; //called when stack gets its turn | ||||
| 	void reorderPossibleActionsPriority(const CStack * stack, MouseHoveredHexContext context); | ||||
|   | ||||
| @@ -158,10 +158,10 @@ ECreatureAnimType AttackAnimation::findValidGroup( const std::vector<ECreatureAn | ||||
|  | ||||
| const CCreature * AttackAnimation::getCreature() const | ||||
| { | ||||
| 	if (attackingStack->getCreature()->getId() == CreatureID::ARROW_TOWERS) | ||||
| 	if (attackingStack->unitType()->getId() == CreatureID::ARROW_TOWERS) | ||||
| 		return owner.siegeController->getTurretCreature(); | ||||
| 	else | ||||
| 		return attackingStack->getCreature(); | ||||
| 		return attackingStack->unitType(); | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -179,7 +179,7 @@ HittedAnimation::HittedAnimation(BattleInterface & owner, const CStack * stack) | ||||
| 	: StackActionAnimation(owner, stack) | ||||
| { | ||||
| 	setGroup(ECreatureAnimType::HITTED); | ||||
| 	setSound(battle_sound(stack->getCreature(), wince)); | ||||
| 	setSound(battle_sound(stack->unitType(), wince)); | ||||
| 	logAnim->debug("Created HittedAnimation for %s", stack->getName()); | ||||
| } | ||||
|  | ||||
| @@ -187,14 +187,14 @@ DefenceAnimation::DefenceAnimation(BattleInterface & owner, const CStack * stack | ||||
| 	: StackActionAnimation(owner, stack) | ||||
| { | ||||
| 	setGroup(ECreatureAnimType::DEFENCE); | ||||
| 	setSound(battle_sound(stack->getCreature(), defend)); | ||||
| 	setSound(battle_sound(stack->unitType(), defend)); | ||||
| 	logAnim->debug("Created DefenceAnimation for %s", stack->getName()); | ||||
| } | ||||
|  | ||||
| DeathAnimation::DeathAnimation(BattleInterface & owner, const CStack * stack, bool ranged): | ||||
| 	StackActionAnimation(owner, stack) | ||||
| { | ||||
| 	setSound(battle_sound(stack->getCreature(), killed)); | ||||
| 	setSound(battle_sound(stack->unitType(), killed)); | ||||
|  | ||||
| 	if(ranged && myAnim->framesInGroup(ECreatureAnimType::DEATH_RANGED) > 0) | ||||
| 		setGroup(ECreatureAnimType::DEATH_RANGED); | ||||
| @@ -356,13 +356,13 @@ bool MovementAnimation::init() | ||||
|  | ||||
| 	if (moveSoundHander == -1) | ||||
| 	{ | ||||
| 		moveSoundHander = CCS->soundh->playSound(battle_sound(stack->getCreature(), move), -1); | ||||
| 		moveSoundHander = CCS->soundh->playSound(battle_sound(stack->unitType(), move), -1); | ||||
| 	} | ||||
|  | ||||
| 	Point begPosition = owner.stacksController->getStackPositionAtHex(prevHex, stack); | ||||
| 	Point endPosition = owner.stacksController->getStackPositionAtHex(nextHex, stack); | ||||
|  | ||||
| 	progressPerSecond = AnimationControls::getMovementDistance(stack->getCreature()); | ||||
| 	progressPerSecond = AnimationControls::getMovementDistance(stack->unitType()); | ||||
|  | ||||
| 	begX = begPosition.x; | ||||
| 	begY = begPosition.y; | ||||
| @@ -373,7 +373,7 @@ bool MovementAnimation::init() | ||||
| 	if (stack->hasBonus(Selector::type()(Bonus::FLYING))) | ||||
| 	{ | ||||
| 		float distance = static_cast<float>(sqrt(distanceX * distanceX + distanceY * distanceY)); | ||||
| 		progressPerSecond =  AnimationControls::getFlightDistance(stack->getCreature()) / distance; | ||||
| 		progressPerSecond =  AnimationControls::getFlightDistance(stack->unitType()) / distance; | ||||
| 	} | ||||
|  | ||||
| 	return true; | ||||
| @@ -453,7 +453,7 @@ bool MovementEndAnimation::init() | ||||
| 	logAnim->debug("CMovementEndAnimation::init: stack %s", stack->getName()); | ||||
| 	myAnim->pos.moveTo(owner.stacksController->getStackPositionAtHex(nextHex, stack)); | ||||
|  | ||||
| 	CCS->soundh->playSound(battle_sound(stack->getCreature(), endMoving)); | ||||
| 	CCS->soundh->playSound(battle_sound(stack->unitType(), endMoving)); | ||||
|  | ||||
| 	if(!myAnim->framesInGroup(ECreatureAnimType::MOVE_END)) | ||||
| 	{ | ||||
| @@ -494,7 +494,7 @@ bool MovementStartAnimation::init() | ||||
| 	} | ||||
|  | ||||
| 	logAnim->debug("CMovementStartAnimation::init: stack %s", stack->getName()); | ||||
| 	CCS->soundh->playSound(battle_sound(stack->getCreature(), startMoving)); | ||||
| 	CCS->soundh->playSound(battle_sound(stack->unitType(), startMoving)); | ||||
|  | ||||
| 	if(!myAnim->framesInGroup(ECreatureAnimType::MOVE_START)) | ||||
| 	{ | ||||
|   | ||||
| @@ -146,7 +146,7 @@ BattleProjectileController::BattleProjectileController(BattleInterface & owner): | ||||
|  | ||||
| const CCreature & BattleProjectileController::getShooter(const CStack * stack) const | ||||
| { | ||||
| 	const CCreature * creature = stack->getCreature(); | ||||
| 	const CCreature * creature = stack->unitType(); | ||||
|  | ||||
| 	if(creature->getId() == CreatureID::ARROW_TOWERS) | ||||
| 		creature = owner.siegeController->getTurretCreature(); | ||||
|   | ||||
| @@ -49,7 +49,7 @@ static void onAnimationFinished(const CStack *stack, std::weak_ptr<CreatureAnima | ||||
|  | ||||
| 	if (animation->isIdle()) | ||||
| 	{ | ||||
| 		const CCreature *creature = stack->getCreature(); | ||||
| 		const CCreature *creature = stack->unitType(); | ||||
|  | ||||
| 		if (stack->isFrozen()) | ||||
| 			animation->setType(ECreatureAnimType::FROZEN); | ||||
| @@ -207,7 +207,7 @@ void BattleStacksController::stackAdded(const CStack * stack, bool instant) | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		stackAnimation[stack->ID] = AnimationControls::getAnimation(stack->getCreature()); | ||||
| 		stackAnimation[stack->ID] = AnimationControls::getAnimation(stack->unitType()); | ||||
| 		stackAnimation[stack->ID]->onAnimationReset += std::bind(&onAnimationFinished, stack, stackAnimation[stack->ID]); | ||||
| 		stackAnimation[stack->ID]->pos.h = stackAnimation[stack->ID]->getHeight(); | ||||
| 		stackAnimation[stack->ID]->pos.w = stackAnimation[stack->ID]->getWidth(); | ||||
|   | ||||
| @@ -140,11 +140,6 @@ | ||||
| 							"appearAnimation" : "C09SPF0", | ||||
| 							"appearSound" : "LANDMINE" | ||||
| 						} | ||||
| 					}, | ||||
| 					"damage":{ | ||||
| 						"type":"core:damage", | ||||
| 						"optional":false, | ||||
| 						"indirect":true | ||||
| 					} | ||||
| 				} | ||||
| 			}, | ||||
| @@ -641,6 +636,21 @@ | ||||
| 					} | ||||
| 				}, | ||||
| 				"targetModifier":{"smart":true} | ||||
| 			}, | ||||
| 			"advanced" :{ | ||||
| 				"battleEffects":{ | ||||
| 					"teleport":{ | ||||
| 						"isMoatPassable" : true | ||||
| 					} | ||||
| 				} | ||||
| 			}, | ||||
| 			"expert" : { | ||||
| 				"battleEffects":{ | ||||
| 					"teleport":{ | ||||
| 						"isWallPassable" : true, | ||||
| 						"isMoatPassable" : true | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"flags" : { | ||||
|   | ||||
| @@ -53,12 +53,8 @@ public: | ||||
| 	 */ | ||||
| 	virtual int32_t getLevelPower(const int32_t skillLevel) const = 0; | ||||
|  | ||||
| 	virtual std::string getNameTextID() const = 0; | ||||
| 	virtual std::string getNameTranslated() const  = 0; | ||||
|  | ||||
| 	virtual std::string getDescriptionTextID(int32_t level) const  = 0; | ||||
| 	virtual std::string getDescriptionTranslated(int32_t level) const  = 0; | ||||
|  | ||||
| }; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -56,11 +56,6 @@ CStack::CStack(const CStackBasicDescriptor * stack, const PlayerColor & O, int I | ||||
| 	health.init(); //??? | ||||
| } | ||||
|  | ||||
| const CCreature * CStack::getCreature() const | ||||
| { | ||||
| 	return type; | ||||
| } | ||||
|  | ||||
| void CStack::localInit(BattleInfo * battleInfo) | ||||
| { | ||||
| 	battle = battleInfo; | ||||
| @@ -88,7 +83,7 @@ ui32 CStack::level() const | ||||
| 	if(base) | ||||
| 		return base->getLevel(); //creature or commander | ||||
| 	else | ||||
| 		return std::max(1, static_cast<int>(getCreature()->getLevel())); //war machine, clone etc | ||||
| 		return std::max(1, static_cast<int>(unitType()->getLevel())); //war machine, clone etc | ||||
| } | ||||
|  | ||||
| si32 CStack::magicResistance() const | ||||
| @@ -346,7 +341,7 @@ bool CStack::unitHasAmmoCart(const battle::Unit * unit) const | ||||
| { | ||||
| 	for(const CStack * st : battle->stacks) | ||||
| 	{ | ||||
| 		if(battle->battleMatchOwner(st, unit, true) && st->getCreature()->getId() == CreatureID::AMMO_CART) | ||||
| 		if(battle->battleMatchOwner(st, unit, true) && st->unitType()->getId() == CreatureID::AMMO_CART) | ||||
| 		{ | ||||
| 			return st->alive(); | ||||
| 		} | ||||
|   | ||||
| @@ -43,8 +43,6 @@ public: | ||||
| 	CStack(); | ||||
| 	~CStack(); | ||||
|  | ||||
| 	const CCreature * getCreature() const; //deprecated | ||||
|  | ||||
| 	std::string nodeName() const override; | ||||
|  | ||||
| 	void localInit(BattleInfo * battleInfo); | ||||
|   | ||||
| @@ -52,7 +52,7 @@ void BattleInfo::calculateCasualties(std::map<ui32,si32> * casualties) const | ||||
| 		const CStack * const st = elem; | ||||
| 		si32 killed = st->getKilled(); | ||||
| 		if(killed > 0) | ||||
| 			casualties[st->side][st->getCreature()->getId()] += killed; | ||||
| 			casualties[st->side][st->unitType()->getId()] += killed; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -210,7 +210,7 @@ BattleInfo * BattleInfo::setupBattle(const int3 & tile, TerrainId terrain, const | ||||
| 	if(town) | ||||
| 	{ | ||||
| 		curB->town = town; | ||||
| 		curB->terrainType = (*VLC->townh)[town->subID]->nativeTerrain; | ||||
| 		curB->terrainType = town->getNativeTerrain(); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
|   | ||||
| @@ -14,9 +14,13 @@ | ||||
|  | ||||
| #include "../CStack.h" | ||||
| #include "BattleInfo.h" | ||||
| #include "CObstacleInstance.h" | ||||
| #include "DamageCalculator.h" | ||||
| #include "PossiblePlayerBattleAction.h" | ||||
| #include "../NetPacks.h" | ||||
| #include "../spells/ObstacleCasterProxy.h" | ||||
| #include "../spells/ISpellMechanics.h" | ||||
| #include "../spells/Problem.h" | ||||
| #include "../spells/CSpellHandler.h" | ||||
| #include "../mapObjects/CGTownInstance.h" | ||||
| #include "../BattleFieldHandler.h" | ||||
| @@ -133,11 +137,13 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastSpell(con | ||||
| 	return ESpellCastProblem::OK; | ||||
| } | ||||
|  | ||||
| bool CBattleInfoCallback::battleHasWallPenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const | ||||
| bool CBattleInfoCallback::battleHasPenaltyOnLine(BattleHex from, BattleHex dest, bool checkWall, bool checkMoat) const | ||||
| { | ||||
| 	auto isTileBlocked = [&](BattleHex tile) | ||||
| 	{ | ||||
| 		EWallPart wallPart = battleHexToWallPart(tile); | ||||
| 		if (wallPart == EWallPart::INVALID) | ||||
| 			return false; // there is no wall here | ||||
| 		if (wallPart == EWallPart::INDESTRUCTIBLE_PART_OF_GATE) | ||||
| 			return false; // does not blocks ranged attacks | ||||
| 		if (wallPart == EWallPart::INDESTRUCTIBLE_PART) | ||||
| @@ -145,35 +151,54 @@ bool CBattleInfoCallback::battleHasWallPenalty(const IBonusBearer * shooter, Bat | ||||
|  | ||||
| 		return isWallPartAttackable(wallPart); | ||||
| 	}; | ||||
|  | ||||
| 	auto needWallPenalty = [&](BattleHex from, BattleHex dest) | ||||
| 	// Count wall penalty requirement by shortest path, not by arbitrary line, to avoid various OH3 bugs | ||||
| 	auto getShortestPath = [](BattleHex from, BattleHex dest) -> std::vector<BattleHex> | ||||
| 	{ | ||||
| 		// arbitrary selected cell size for virtual grid | ||||
| 		// any even number can be selected (for division by two) | ||||
| 		static const int cellSize = 10; | ||||
| 		//Out early | ||||
| 		if(from == dest) | ||||
| 			return {}; | ||||
|  | ||||
| 		// create line that goes from center of shooter cell to center of target cell | ||||
| 		Point line1{ from.getX()*cellSize+cellSize/2, from.getY()*cellSize+cellSize/2}; | ||||
| 		Point line2{ dest.getX()*cellSize+cellSize/2, dest.getY()*cellSize+cellSize/2}; | ||||
| 		std::vector<BattleHex> ret; | ||||
| 		auto next = from; | ||||
| 		//Not a real direction, only to indicate to which side we should search closest tile | ||||
| 		auto direction = from.getX() > dest.getX() ? BattleSide::DEFENDER : BattleSide::ATTACKER; | ||||
|  | ||||
| 		for (int y = 0; y < GameConstants::BFIELD_HEIGHT; ++y) | ||||
| 		while (next != dest) | ||||
| 		{ | ||||
| 			BattleHex obstacle = lineToWallHex(y); | ||||
| 			if (!isTileBlocked(obstacle)) | ||||
| 				continue; | ||||
|  | ||||
| 			// create rect around cell with an obstacle | ||||
| 			Rect rect { | ||||
| 				Point(obstacle.getX(), obstacle.getY()) * cellSize, | ||||
| 				Point( cellSize, cellSize) | ||||
| 			}; | ||||
|  | ||||
| 			if ( rect.intersectionTest(line1, line2)) | ||||
| 				return true; | ||||
| 			auto tiles = next.neighbouringTiles(); | ||||
| 			std::set<BattleHex> possibilities = {tiles.begin(), tiles.end()}; | ||||
| 			next = BattleHex::getClosestTile(direction, dest, possibilities); | ||||
| 			ret.push_back(next); | ||||
| 		} | ||||
| 		return false; | ||||
| 		assert(!ret.empty()); | ||||
| 		ret.pop_back(); //Remove destination hex | ||||
| 		return ret; | ||||
| 	}; | ||||
|  | ||||
| 	RETURN_IF_NOT_BATTLE(false); | ||||
| 	auto checkNeeded = !sameSideOfWall(from, dest); | ||||
| 	bool pathHasWall = false; | ||||
| 	bool pathHasMoat = false; | ||||
|  | ||||
| 	for(const auto & hex : getShortestPath(from, dest)) | ||||
| 	{ | ||||
| 		pathHasWall |= isTileBlocked(hex); | ||||
| 		if(!checkMoat) | ||||
| 			continue; | ||||
|  | ||||
| 		auto obstacles = battleGetAllObstaclesOnPos(hex, false); | ||||
|  | ||||
| 		if(hex != ESiegeHex::GATE_BRIDGE || (battleIsGatePassable())) | ||||
| 			for(const auto & obst : obstacles) | ||||
| 				if(obst->obstacleType ==  CObstacleInstance::MOAT) | ||||
| 					pathHasMoat |= true; | ||||
| 	} | ||||
|  | ||||
| 	return checkNeeded && ( (checkWall && pathHasWall) || (checkMoat && pathHasMoat) ); | ||||
| } | ||||
|  | ||||
| bool CBattleInfoCallback::battleHasWallPenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const | ||||
| { | ||||
| 	RETURN_IF_NOT_BATTLE(false); | ||||
| 	if(!battleGetSiegeLevel()) | ||||
| 		return false; | ||||
| @@ -184,26 +209,9 @@ bool CBattleInfoCallback::battleHasWallPenalty(const IBonusBearer * shooter, Bat | ||||
| 	if(shooter->hasBonus(selectorNoWallPenalty, cachingStrNoWallPenalty)) | ||||
| 		return false; | ||||
|  | ||||
| 	const int wallInStackLine = lineToWallHex(shooterPosition.getY()); | ||||
| 	const bool shooterOutsideWalls = shooterPosition < wallInStackLine; | ||||
| 	const auto shooterOutsideWalls = shooterPosition < lineToWallHex(shooterPosition.getY()); | ||||
|  | ||||
| 	return shooterOutsideWalls && needWallPenalty(shooterPosition, destHex); | ||||
| } | ||||
|  | ||||
| si8 CBattleInfoCallback::battleCanTeleportTo(const battle::Unit * stack, BattleHex destHex, int telportLevel) const | ||||
| { | ||||
| 	RETURN_IF_NOT_BATTLE(false); | ||||
| 	if (!getAccesibility(stack).accessible(destHex, stack)) | ||||
| 		return false; | ||||
|  | ||||
| 	const ui8 siegeLevel = battleGetSiegeLevel(); | ||||
|  | ||||
| 	//check for wall | ||||
| 	//advanced teleport can pass wall of fort|citadel, expert - of castle | ||||
| 	if ((siegeLevel > CGTownInstance::NONE && telportLevel < 2) || (siegeLevel >= CGTownInstance::CASTLE && telportLevel < 3)) | ||||
| 		return sameSideOfWall(stack->getPosition(), destHex); | ||||
|  | ||||
| 	return true; | ||||
| 	return shooterOutsideWalls && battleHasPenaltyOnLine(shooterPosition, destHex, true, false); | ||||
| } | ||||
|  | ||||
| std::vector<PossiblePlayerBattleAction> CBattleInfoCallback::getClientActionsForStack(const CStack * stack, const BattleClientInterfaceData & data) | ||||
| @@ -271,7 +279,7 @@ PossiblePlayerBattleAction CBattleInfoCallback::getCasterAction(const CSpell * s | ||||
| 	return PossiblePlayerBattleAction(spellSelMode, spell->id); | ||||
| } | ||||
|  | ||||
| std::set<BattleHex> CBattleInfoCallback::battleGetAttackedHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos) const | ||||
| std::set<BattleHex> CBattleInfoCallback::battleGetAttackedHexes(const battle::Unit * attacker, BattleHex destinationTile, BattleHex attackerPos) const | ||||
| { | ||||
| 	std::set<BattleHex> attackedHexes; | ||||
| 	RETURN_IF_NOT_BATTLE(attackedHexes); | ||||
| @@ -645,7 +653,7 @@ std::vector<BattleHex> CBattleInfoCallback::battleGetAvailableHexes(const battle | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| bool CBattleInfoCallback::battleCanAttack(const CStack * stack, const CStack * target, BattleHex dest) const | ||||
| bool CBattleInfoCallback::battleCanAttack(const battle::Unit * stack, const battle::Unit * target, BattleHex dest) const | ||||
| { | ||||
| 	RETURN_IF_NOT_BATTLE(false); | ||||
|  | ||||
| @@ -658,7 +666,7 @@ bool CBattleInfoCallback::battleCanAttack(const CStack * stack, const CStack * t | ||||
| 	if(!battleMatchOwner(stack, target)) | ||||
| 		return false; | ||||
|  | ||||
| 	auto id = stack->getCreature()->getId(); | ||||
| 	auto id = stack->unitType()->getId(); | ||||
| 	if (id == CreatureID::FIRST_AID_TENT || id == CreatureID::CATAPULT) | ||||
| 		return false; | ||||
|  | ||||
| @@ -814,15 +822,74 @@ std::vector<std::shared_ptr<const CObstacleInstance>> CBattleInfoCallback::getAl | ||||
| 						affectedObstacles.push_back(i); | ||||
| 		} | ||||
| 		for(auto hex : unit->getHexes()) | ||||
| 			if(hex == ESiegeHex::GATE_BRIDGE) | ||||
| 				if(battleGetGateState() == EGateState::OPENED || battleGetGateState() == EGateState::DESTROYED) | ||||
| 					for(int i=0; i<affectedObstacles.size(); i++) | ||||
| 						if(affectedObstacles.at(i)->obstacleType == CObstacleInstance::MOAT) | ||||
| 							affectedObstacles.erase(affectedObstacles.begin()+i); | ||||
| 			if(hex == ESiegeHex::GATE_BRIDGE && battleIsGatePassable()) | ||||
| 				for(int i=0; i<affectedObstacles.size(); i++) | ||||
| 					if(affectedObstacles.at(i)->obstacleType == CObstacleInstance::MOAT) | ||||
| 						affectedObstacles.erase(affectedObstacles.begin()+i); | ||||
| 	} | ||||
| 	return affectedObstacles; | ||||
| } | ||||
|  | ||||
| bool CBattleInfoCallback::handleObstacleTriggersForUnit(SpellCastEnvironment & spellEnv, const battle::Unit & unit, const std::set<BattleHex> & passed) const | ||||
| { | ||||
| 	if(!unit.alive()) | ||||
| 		return false; | ||||
| 	bool movementStopped = false; | ||||
| 	for(auto & obstacle : getAllAffectedObstaclesByStack(&unit, passed)) | ||||
| 	{ | ||||
| 		//helper info | ||||
| 		const SpellCreatedObstacle * spellObstacle = dynamic_cast<const SpellCreatedObstacle *>(obstacle.get()); | ||||
|  | ||||
| 		if(spellObstacle) | ||||
| 		{ | ||||
| 			auto revealObstacles = [&](const SpellCreatedObstacle & spellObstacle) -> void | ||||
| 			{ | ||||
| 				// For the hidden spell created obstacles, e.g. QuickSand, it should be revealed after taking damage | ||||
| 				auto operation = ObstacleChanges::EOperation::UPDATE; | ||||
| 				if (spellObstacle.removeOnTrigger) | ||||
| 					operation = ObstacleChanges::EOperation::REMOVE; | ||||
|  | ||||
| 				SpellCreatedObstacle changedObstacle; | ||||
| 				changedObstacle.uniqueID = spellObstacle.uniqueID; | ||||
| 				changedObstacle.revealed = true; | ||||
|  | ||||
| 				BattleObstaclesChanged bocp; | ||||
| 				bocp.changes.emplace_back(spellObstacle.uniqueID, operation); | ||||
| 				changedObstacle.toInfo(bocp.changes.back(), operation); | ||||
| 				spellEnv.apply(&bocp); | ||||
| 			}; | ||||
| 			const auto side = unit.unitSide(); | ||||
| 			auto shouldReveal = !spellObstacle->hidden || !battleIsObstacleVisibleForSide(*obstacle, (BattlePerspective::BattlePerspective)side); | ||||
| 			const auto * hero = battleGetFightingHero(spellObstacle->casterSide); | ||||
| 			auto caster = spells::ObstacleCasterProxy(getBattle()->getSidePlayer(spellObstacle->casterSide), hero, *spellObstacle); | ||||
| 			const auto * sp = obstacle->getTrigger().toSpell(); | ||||
| 			if(obstacle->triggersEffects() && sp) | ||||
| 			{ | ||||
| 				auto cast = spells::BattleCast(this, &caster, spells::Mode::PASSIVE, sp); | ||||
| 				spells::detail::ProblemImpl ignored; | ||||
| 				auto target = spells::Target(1, spells::Destination(&unit)); | ||||
| 				if(sp->battleMechanics(&cast)->canBeCastAt(target, ignored)) // Obstacles should not be revealed by immune creatures | ||||
| 				{ | ||||
| 					if(shouldReveal) { //hidden obstacle triggers effects after revealed | ||||
| 						revealObstacles(*spellObstacle); | ||||
| 						cast.cast(&spellEnv, target); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			else if(shouldReveal) | ||||
| 				revealObstacles(*spellObstacle); | ||||
| 		} | ||||
|  | ||||
| 		if(!unit.alive()) | ||||
| 			return false; | ||||
|  | ||||
| 		if(obstacle->stopsMovement()) | ||||
| 			movementStopped = true; | ||||
| 	} | ||||
|  | ||||
| 	return unit.alive() && !movementStopped; | ||||
| } | ||||
|  | ||||
| AccessibilityInfo CBattleInfoCallback::getAccesibility() const | ||||
| { | ||||
| 	AccessibilityInfo ret; | ||||
|   | ||||
| @@ -20,6 +20,7 @@ VCMI_LIB_NAMESPACE_BEGIN | ||||
| class CGHeroInstance; | ||||
| class CStack; | ||||
| class ISpellCaster; | ||||
| class SpellCastEnvironment; | ||||
| class CSpell; | ||||
| struct CObstacleInstance; | ||||
| class IBonusBearer; | ||||
| @@ -61,6 +62,8 @@ public: | ||||
|  | ||||
| 	std::vector<std::shared_ptr<const CObstacleInstance>> battleGetAllObstaclesOnPos(BattleHex tile, bool onlyBlocking = true) const override; | ||||
| 	std::vector<std::shared_ptr<const CObstacleInstance>> getAllAffectedObstaclesByStack(const battle::Unit * unit, const std::set<BattleHex> & passed) const override; | ||||
| 	//Handle obstacle damage here, requires SpellCastEnvironment | ||||
| 	bool handleObstacleTriggersForUnit(SpellCastEnvironment & spellEnv, const battle::Unit & unit, const std::set<BattleHex> & passed = {}) const; | ||||
|  | ||||
| 	const CStack * battleGetStackByPos(BattleHex pos, bool onlyAlive = true) const; | ||||
|  | ||||
| @@ -83,10 +86,10 @@ public: | ||||
|  | ||||
| 	int battleGetSurrenderCost(const PlayerColor & Player) const; //returns cost of surrendering battle, -1 if surrendering is not possible | ||||
| 	ReachabilityInfo::TDistances battleGetDistances(const battle::Unit * unit, BattleHex assumedPosition) const; | ||||
| 	std::set<BattleHex> battleGetAttackedHexes(const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID) const; | ||||
| 	std::set<BattleHex> battleGetAttackedHexes(const battle::Unit * attacker, BattleHex destinationTile, BattleHex attackerPos = BattleHex::INVALID) const; | ||||
| 	bool isEnemyUnitWithinSpecifiedRange(BattleHex attackerPosition, const battle::Unit * defenderUnit, unsigned int range) const; | ||||
|  | ||||
| 	bool battleCanAttack(const CStack * stack, const CStack * target, BattleHex dest) const; //determines if stack with given ID can attack target at the selected destination | ||||
| 	bool battleCanAttack(const battle::Unit * stack, const battle::Unit * target, BattleHex dest) const; //determines if stack with given ID can attack target at the selected destination | ||||
| 	bool battleCanShoot(const battle::Unit * attacker, BattleHex dest) const; //determines if stack with given ID shoot at the selected destination | ||||
| 	bool battleCanShoot(const battle::Unit * attacker) const; //determines if stack with given ID shoot in principle | ||||
| 	bool battleIsUnitBlocked(const battle::Unit * unit) const; //returns true if there is neighboring enemy stack | ||||
| @@ -101,6 +104,7 @@ public: | ||||
| 	DamageEstimation battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, BattleHex attackerPosition, DamageEstimation * retaliationDmg = nullptr) const; | ||||
| 	DamageEstimation battleEstimateDamage(const battle::Unit * attacker, const battle::Unit * defender, int movementDistance, DamageEstimation * retaliationDmg = nullptr) const; | ||||
|  | ||||
| 	bool battleHasPenaltyOnLine(BattleHex from, BattleHex dest, bool checkWall, bool checkMoat) const; | ||||
| 	bool battleHasDistancePenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const; | ||||
| 	bool battleHasWallPenalty(const IBonusBearer * shooter, BattleHex shooterPosition, BattleHex destHex) const; | ||||
| 	bool battleHasShootingPenalty(const battle::Unit * shooter, BattleHex destHex) const; | ||||
| @@ -120,7 +124,6 @@ public: | ||||
| 	SpellID getRandomBeneficialSpell(CRandomGenerator & rand, const CStack * subject) const; | ||||
| 	SpellID getRandomCastedSpell(CRandomGenerator & rand, const CStack * caster) const; //called at the beginning of turn for Faerie Dragon | ||||
|  | ||||
| 	si8 battleCanTeleportTo(const battle::Unit * stack, BattleHex destHex, int telportLevel) const; //checks if teleportation of given stack to given position can take place | ||||
| 	std::vector<PossiblePlayerBattleAction> getClientActionsForStack(const CStack * stack, const BattleClientInterfaceData & data); | ||||
| 	PossiblePlayerBattleAction getCasterAction(const CSpell * spell, const spells::Caster * caster, spells::Mode mode) const; | ||||
|  | ||||
|   | ||||
| @@ -379,6 +379,15 @@ EGateState CBattleInfoEssentials::battleGetGateState() const | ||||
| 	return getBattle()->getGateState(); | ||||
| } | ||||
|  | ||||
| bool CBattleInfoEssentials::battleIsGatePassable() const | ||||
| { | ||||
| 	RETURN_IF_NOT_BATTLE(true); | ||||
| 	if(battleGetSiegeLevel() == CGTownInstance::NONE) | ||||
| 		return true; | ||||
|  | ||||
| 	return battleGetGateState() == EGateState::OPENED || battleGetGateState() == EGateState::DESTROYED;  | ||||
| } | ||||
|  | ||||
| PlayerColor CBattleInfoEssentials::battleGetOwner(const battle::Unit * unit) const | ||||
| { | ||||
| 	RETURN_IF_NOT_BATTLE(PlayerColor::CANNOT_DETERMINE); | ||||
|   | ||||
| @@ -96,6 +96,7 @@ public: | ||||
| 	// [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 | ||||
| 	EWallState battleGetWallState(EWallPart partOfWall) const; | ||||
| 	EGateState battleGetGateState() const; | ||||
| 	bool battleIsGatePassable() const; | ||||
|  | ||||
| 	//helpers | ||||
| 	///returns all stacks, alive or dead or undead or mechanical :) | ||||
|   | ||||
| @@ -25,14 +25,13 @@ class DLL_LINKAGE CCallbackBase | ||||
| { | ||||
| 	const IBattleInfo * battle = nullptr; //battle to which the player is engaged, nullptr if none or not applicable | ||||
|  | ||||
| 	const IBattleInfo * getBattle() const; | ||||
|  | ||||
| protected: | ||||
| 	boost::optional<PlayerColor> player; // not set gives access to all information, otherwise callback provides only information "visible" for player | ||||
|  | ||||
| 	CCallbackBase(boost::optional<PlayerColor> Player); | ||||
| 	CCallbackBase() = default; | ||||
|  | ||||
| 	const IBattleInfo * getBattle() const; | ||||
| 	void setBattle(const IBattleInfo * B); | ||||
| 	bool duringBattle() const; | ||||
|  | ||||
|   | ||||
| @@ -14,6 +14,7 @@ | ||||
| #include "../ISpellMechanics.h" | ||||
| #include "../../NetPacks.h" | ||||
| #include "../../battle/CBattleInfoCallback.h" | ||||
| #include "../../serializer/JsonSerializeFormat.h" | ||||
| #include "../../battle/Unit.h" | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_BEGIN | ||||
| @@ -45,39 +46,34 @@ void Teleport::adjustTargetTypes(std::vector<TargetType> & types) const | ||||
| 	} | ||||
| } | ||||
|  | ||||
| bool Teleport::applicable(Problem & problem, const Mechanics * m) const | ||||
| bool Teleport::applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const | ||||
| { | ||||
| 	return UnitEffect::applicable(problem, m); | ||||
| 	if(target.size() == 1) //Assume, this is check only for selecting a unit | ||||
| 		return UnitEffect::applicable(problem, m); | ||||
|  | ||||
| 	if(target.size() != 2) | ||||
| 		return m->adaptProblem(ESpellCastProblem::WRONG_SPELL_TARGET, problem); | ||||
|  | ||||
| 	const auto *targetUnit = target[0].unitValue; | ||||
| 	const auto & targetHex = target[1].hexValue; | ||||
|  | ||||
| 	if(!targetUnit) | ||||
| 		return m->adaptProblem(ESpellCastProblem::WRONG_SPELL_TARGET, problem); | ||||
|  | ||||
| 	if(!targetHex.isValid() || !m->battle()->getAccesibility(targetUnit).accessible(targetHex, targetUnit)) | ||||
| 		return m->adaptProblem(ESpellCastProblem::WRONG_SPELL_TARGET, problem); | ||||
|  | ||||
| 	if(m->battle()->battleGetSiegeLevel() && !(isWallPassable && isMoatPassable)) | ||||
| 	{ | ||||
| 		return !m->battle()->battleHasPenaltyOnLine(target[0].hexValue, target[1].hexValue, !isWallPassable, !isMoatPassable); | ||||
| 	} | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| void Teleport::apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const | ||||
| { | ||||
| 	if(target.size() != 2) | ||||
| 	{ | ||||
| 		server->complain("Teleport requires 2 destinations."); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	const auto *targetUnit = target[0].unitValue; | ||||
| 	if(nullptr == targetUnit) | ||||
| 	{ | ||||
| 		server->complain("No unit to teleport"); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	const BattleHex destination = target[1].hexValue; | ||||
| 	if(!destination.isValid()) | ||||
| 	{ | ||||
| 		server->complain("Invalid teleport destination"); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	//TODO: move here all teleport checks | ||||
| 	if(!m->battle()->battleCanTeleportTo(targetUnit, destination, m->getEffectLevel())) | ||||
| 	{ | ||||
| 		server->complain("Forbidden teleport."); | ||||
| 		return; | ||||
| 	} | ||||
| 	const auto destination = target[1].hexValue; | ||||
|  | ||||
| 	BattleStackMoved pack; | ||||
| 	pack.distance = 0; | ||||
| @@ -87,11 +83,19 @@ void Teleport::apply(ServerCallback * server, const Mechanics * m, const EffectT | ||||
| 	pack.tilesToMove = tiles; | ||||
| 	pack.teleporting = true; | ||||
| 	server->apply(&pack); | ||||
|  | ||||
| 	if(triggerObstacles) | ||||
| 	{ | ||||
| 		auto spellEnv = dynamic_cast<SpellCastEnvironment*>(server); | ||||
| 		m->battle()->handleObstacleTriggersForUnit(*spellEnv, *targetUnit); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void Teleport::serializeJsonUnitEffect(JsonSerializeFormat & handler) | ||||
| { | ||||
| 	//TODO: teleport options | ||||
| 	handler.serializeBool("triggerObstacles", triggerObstacles); | ||||
| 	handler.serializeBool("isWallPassable", isWallPassable); | ||||
| 	handler.serializeBool("isMoatPassable", isMoatPassable); | ||||
| } | ||||
|  | ||||
| EffectTarget Teleport::transformTarget(const Mechanics * m, const Target & aimPoint, const Target & spellTarget) const | ||||
|   | ||||
| @@ -26,7 +26,7 @@ class Teleport : public UnitEffect | ||||
| public: | ||||
| 	void adjustTargetTypes(std::vector<TargetType> & types) const override; | ||||
|  | ||||
| 	bool applicable(Problem & problem, const Mechanics * m) const override; | ||||
| 	bool applicable(Problem & problem, const Mechanics * m, const EffectTarget & target) const override; | ||||
|  | ||||
| 	void apply(ServerCallback * server, const Mechanics * m, const EffectTarget & target) const override; | ||||
|  | ||||
| @@ -34,6 +34,11 @@ public: | ||||
|  | ||||
| protected: | ||||
| 	void serializeJsonUnitEffect(JsonSerializeFormat & handler) override; | ||||
|  | ||||
| private: | ||||
| 	bool triggerObstacles; | ||||
| 	bool isWallPassable; | ||||
| 	bool isMoatPassable; | ||||
| }; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1208,11 +1208,11 @@ void CGameHandler::addGenericKilledLog(BattleLogMessage & blm, const CStack * de | ||||
| 		boost::format txt(formatString); | ||||
| 		if(killed > 1) | ||||
| 		{ | ||||
| 			txt % killed % (multiple ? VLC->generaltexth->allTexts[43] : defender->getCreature()->getNamePluralTranslated()); // creatures perish | ||||
| 			txt % killed % (multiple ? VLC->generaltexth->allTexts[43] : defender->unitType()->getNamePluralTranslated()); // creatures perish | ||||
| 		} | ||||
| 		else //killed == 1 | ||||
| 		{ | ||||
| 			txt % (multiple ? VLC->generaltexth->allTexts[42] : defender->getCreature()->getNameSingularTranslated()); // creature perishes | ||||
| 			txt % (multiple ? VLC->generaltexth->allTexts[42] : defender->unitType()->getNameSingularTranslated()); // creature perishes | ||||
| 		} | ||||
| 		MetaString line; | ||||
| 		line << txt.str(); | ||||
| @@ -1527,7 +1527,7 @@ int CGameHandler::moveStack(int stack, BattleHex dest) | ||||
| 			{ | ||||
| 				if(stackIsMoving && start != curStack->getPosition()) | ||||
| 				{ | ||||
| 					stackIsMoving = handleDamageFromObstacle(curStack, passed); | ||||
| 					stackIsMoving = handleObstacleTriggersForUnit(*spellEnv, *curStack, passed); | ||||
| 					passed.insert(curStack->getPosition()); | ||||
| 					if(curStack->doubleWide()) | ||||
| 						passed.insert(curStack->occupiedHex()); | ||||
| @@ -1565,7 +1565,7 @@ int CGameHandler::moveStack(int stack, BattleHex dest) | ||||
| 			passed.clear(); //Just empty passed, obstacles will handled automatically | ||||
| 	} | ||||
| 	//handling obstacle on the final field (separate, because it affects both flying and walking stacks) | ||||
| 	handleDamageFromObstacle(curStack, passed); | ||||
| 	handleObstacleTriggersForUnit(*spellEnv, *curStack, passed); | ||||
|  | ||||
| 	return ret; | ||||
| } | ||||
| @@ -4478,10 +4478,10 @@ void CGameHandler::updateGateState() | ||||
| 	// - deals moat damage to attacker if bridge is closed (fortress only) | ||||
|  | ||||
| 	bool hasForceFieldOnBridge = !battleGetAllObstaclesOnPos(BattleHex(ESiegeHex::GATE_BRIDGE), true).empty(); | ||||
| 	bool hasStackAtGateInner   = gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_INNER), false) != nullptr; | ||||
| 	bool hasStackAtGateOuter   = gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_OUTER), false) != nullptr; | ||||
| 	bool hasStackAtGateBridge  = gs->curB->battleGetStackByPos(BattleHex(ESiegeHex::GATE_BRIDGE), false) != nullptr; | ||||
| 	bool hasWideMoat         = vstd::contains_if(battleGetAllObstaclesOnPos(BattleHex(ESiegeHex::GATE_BRIDGE), false), [](const std::shared_ptr<const CObstacleInstance> & obst) | ||||
| 	bool hasStackAtGateInner   = gs->curB->battleGetUnitByPos(BattleHex(ESiegeHex::GATE_INNER), false) != nullptr; | ||||
| 	bool hasStackAtGateOuter   = gs->curB->battleGetUnitByPos(BattleHex(ESiegeHex::GATE_OUTER), false) != nullptr; | ||||
| 	bool hasStackAtGateBridge  = gs->curB->battleGetUnitByPos(BattleHex(ESiegeHex::GATE_BRIDGE), false) != nullptr; | ||||
| 	bool hasWideMoat           = vstd::contains_if(battleGetAllObstaclesOnPos(BattleHex(ESiegeHex::GATE_BRIDGE), false), [](const std::shared_ptr<const CObstacleInstance> & obst) | ||||
| 	{ | ||||
| 		return obst->obstacleType == CObstacleInstance::MOAT; | ||||
| 	}); | ||||
| @@ -4933,7 +4933,7 @@ bool CGameHandler::makeBattleAction(BattleAction &ba) | ||||
| 	} | ||||
| 	if(ba.actionType == EActionType::WAIT || ba.actionType == EActionType::DEFEND | ||||
| 			|| ba.actionType == EActionType::SHOOT || ba.actionType == EActionType::MONSTER_SPELL) | ||||
| 		handleDamageFromObstacle(stack); | ||||
| 		handleObstacleTriggersForUnit(*spellEnv, *stack); | ||||
| 	if(ba.stackNumber == gs->curB->activeStack || battleResult.get()) //active stack has moved or battle has finished | ||||
| 		battleMadeAction.setn(true); | ||||
| 	return ok; | ||||
| @@ -5289,66 +5289,6 @@ void CGameHandler::stackTurnTrigger(const CStack *st) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| bool CGameHandler::handleDamageFromObstacle(const battle::Unit * curStack, const std::set<BattleHex> & passed) | ||||
| { | ||||
| 	if(!curStack->alive()) | ||||
| 		return false; | ||||
| 	bool movementStopped = false; | ||||
| 	for(auto & obstacle : getAllAffectedObstaclesByStack(curStack, passed)) | ||||
| 	{ | ||||
| 		//helper info | ||||
| 		const SpellCreatedObstacle * spellObstacle = dynamic_cast<const SpellCreatedObstacle *>(obstacle.get()); | ||||
|  | ||||
| 		if(spellObstacle) | ||||
| 		{ | ||||
| 			auto revealObstacles = [&](const SpellCreatedObstacle & spellObstacle) -> void | ||||
| 			{ | ||||
| 				// For the hidden spell created obstacles, e.g. QuickSand, it should be revealed after taking damage | ||||
| 				auto operation = ObstacleChanges::EOperation::UPDATE; | ||||
| 				if (spellObstacle.removeOnTrigger) | ||||
| 					operation = ObstacleChanges::EOperation::REMOVE; | ||||
|  | ||||
| 				SpellCreatedObstacle changedObstacle; | ||||
| 				changedObstacle.uniqueID = spellObstacle.uniqueID; | ||||
| 				changedObstacle.revealed = true; | ||||
|  | ||||
| 				BattleObstaclesChanged bocp; | ||||
| 				bocp.changes.emplace_back(spellObstacle.uniqueID, operation); | ||||
| 				changedObstacle.toInfo(bocp.changes.back(), operation); | ||||
| 				sendAndApply(&bocp); | ||||
| 			}; | ||||
| 			const auto side = curStack->unitSide(); | ||||
| 			auto shouldReveal = !spellObstacle->hidden || !gs->curB->battleIsObstacleVisibleForSide(*obstacle, (BattlePerspective::BattlePerspective)side); | ||||
| 			const auto * hero = gs->curB->battleGetFightingHero(spellObstacle->casterSide); | ||||
| 			auto caster = spells::ObstacleCasterProxy(gs->curB->getSidePlayer(spellObstacle->casterSide), hero, *spellObstacle); | ||||
| 			const auto * sp = obstacle->getTrigger().toSpell(); | ||||
| 			if(obstacle->triggersEffects() && sp) | ||||
| 			{ | ||||
| 				auto cast = spells::BattleCast(gs->curB, &caster, spells::Mode::PASSIVE, sp); | ||||
| 				spells::detail::ProblemImpl ignored; | ||||
| 				auto target = spells::Target(1, spells::Destination(curStack)); | ||||
| 				if(sp->battleMechanics(&cast)->canBeCastAt(target, ignored)) // Obstacles should not be revealed by immune creatures | ||||
| 				{ | ||||
| 					if(shouldReveal) { //hidden obstacle triggers effects after revealed | ||||
| 						revealObstacles(*spellObstacle); | ||||
| 						cast.cast(spellEnv, target); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			else if(shouldReveal) | ||||
| 				revealObstacles(*spellObstacle); | ||||
| 		} | ||||
|  | ||||
| 		if(!curStack->alive()) | ||||
| 			return false; | ||||
|  | ||||
| 		if(obstacle->stopsMovement()) | ||||
| 			movementStopped = true; | ||||
| 	} | ||||
|  | ||||
| 	return curStack->alive() && !movementStopped; | ||||
| } | ||||
|  | ||||
| void CGameHandler::handleTimeEvents() | ||||
| { | ||||
| 	gs->map->events.sort(evntCmp); | ||||
| @@ -6031,8 +5971,8 @@ void CGameHandler::handleAfterAttackCasting(bool ranged, const CStack * attacker | ||||
|  | ||||
| 		int bonusAdditionalInfo = attacker->getBonus(Selector::type()(Bonus::TRANSMUTATION))->additionalInfo[0]; | ||||
|  | ||||
| 		if(defender->getCreature()->getId() == bonusAdditionalInfo || | ||||
| 			(bonusAdditionalInfo == CAddInfo::NONE && defender->getCreature()->getId() == attacker->getCreature()->getId())) | ||||
| 		if(defender->unitType()->getId() == bonusAdditionalInfo || | ||||
| 			(bonusAdditionalInfo == CAddInfo::NONE && defender->unitType()->getId() == attacker->unitType()->getId())) | ||||
| 			return; | ||||
|  | ||||
| 		battle::UnitInfo resurrectInfo; | ||||
| @@ -6421,7 +6361,7 @@ void CGameHandler::runBattle() | ||||
| 			auto accessibility = getAccesibility(); | ||||
| 			CreatureID creatureData = CreatureID(summonInfo->subtype); | ||||
| 			std::vector<BattleHex> targetHexes; | ||||
| 			const bool targetIsBig = stack->getCreature()->isDoubleWide(); //target = creature to guard | ||||
| 			const bool targetIsBig = stack->unitType()->isDoubleWide(); //target = creature to guard | ||||
| 			const bool guardianIsBig = creatureData.toCreature()->isDoubleWide(); | ||||
|  | ||||
| 			/*Chosen idea for two hex units was to cover all possible surrounding hexes of target unit with as small number of stacks as possible. | ||||
| @@ -6606,7 +6546,7 @@ void CGameHandler::runBattle() | ||||
| 			} | ||||
|  | ||||
| 			const CGHeroInstance * curOwner = battleGetOwnerHero(next); | ||||
| 			const int stackCreatureId = next->getCreature()->getId(); | ||||
| 			const int stackCreatureId = next->unitType()->getId(); | ||||
|  | ||||
| 			if ((stackCreatureId == CreatureID::ARROW_TOWERS || stackCreatureId == CreatureID::BALLISTA) | ||||
| 				&& (!curOwner || getRandomGenerator().nextInt(99) >= curOwner->valOfBonuses(Bonus::MANUAL_CONTROL, stackCreatureId))) | ||||
| @@ -6622,7 +6562,7 @@ void CGameHandler::runBattle() | ||||
|  | ||||
| 				for(auto & elem : gs->curB->stacks) | ||||
| 				{ | ||||
| 					if(elem->getCreature()->getId() != CreatureID::CATAPULT | ||||
| 					if(elem->unitType()->getId() != CreatureID::CATAPULT | ||||
| 						&& elem->owner != next->owner | ||||
| 						&& elem->isValidTarget() | ||||
| 						&& gs->curB->battleCanShoot(next, elem->getPosition())) | ||||
| @@ -6644,7 +6584,7 @@ void CGameHandler::runBattle() | ||||
| 				continue; | ||||
| 			} | ||||
|  | ||||
| 			if (next->getCreature()->getId() == CreatureID::CATAPULT) | ||||
| 			if (next->unitType()->getId() == CreatureID::CATAPULT) | ||||
| 			{ | ||||
| 				const auto & attackableBattleHexes = curB.getAttackableBattleHexes(); | ||||
|  | ||||
| @@ -6666,7 +6606,7 @@ void CGameHandler::runBattle() | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if (next->getCreature()->getId() == CreatureID::FIRST_AID_TENT) | ||||
| 			if (next->unitType()->getId() == CreatureID::FIRST_AID_TENT) | ||||
| 			{ | ||||
| 				TStacks possibleStacks = battleGetStacksIf([=](const CStack * s) | ||||
| 				{ | ||||
|   | ||||
| @@ -236,7 +236,6 @@ public: | ||||
| 	bool makeCustomAction(BattleAction &ba); | ||||
| 	void stackEnchantedTrigger(const CStack * stack); | ||||
| 	void stackTurnTrigger(const CStack *stack); | ||||
| 	bool handleDamageFromObstacle(const battle::Unit * curStack, const std::set<BattleHex> & passed = {}); //checks if obstacle is land mine and handles possible consequences | ||||
|  | ||||
| 	void removeObstacle(const CObstacleInstance &obstacle); | ||||
| 	bool queryReply( QueryID qid, const JsonNode & answer, PlayerColor player ); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user