mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Start of stabilization - battles now start correctly
This commit is contained in:
		| @@ -292,7 +292,7 @@ void CBattleAI::activeStack( const CStack * stack ) | ||||
| 			//spellcast may finish battle or kill active stack | ||||
| 			//send special preudo-action | ||||
| 			BattleAction cancel; | ||||
| 			cancel.actionType = EActionType::CANCEL; | ||||
| 			cancel.actionType = EActionType::NO_ACTION; | ||||
| 			cb->battleMakeUnitAction(cancel); | ||||
| 			return; | ||||
| 		} | ||||
|   | ||||
| @@ -206,7 +206,7 @@ bool CCallback::buildBuilding(const CGTownInstance *town, BuildingID buildingID) | ||||
| void CBattleCallback::battleMakeSpellAction(const BattleAction & action) | ||||
| { | ||||
| 	assert(action.actionType == EActionType::HERO_SPELL); | ||||
| 	MakeCustomAction mca(action); | ||||
| 	MakeAction mca(action); | ||||
| 	sendRequest(&mca); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -401,7 +401,7 @@ void BattleStacksController::stackRemoved(uint32_t stackID) | ||||
| 	{ | ||||
| 		BattleAction action; | ||||
| 		action.side = owner.defendingHeroInstance ? (owner.curInt->playerID == owner.defendingHeroInstance->tempOwner) : false; | ||||
| 		action.actionType = EActionType::CANCEL; | ||||
| 		action.actionType = EActionType::NO_ACTION; | ||||
| 		action.stackNumber = getActiveStack()->unitId(); | ||||
|  | ||||
| 		LOCPLINT->cb->battleMakeUnitAction(action); | ||||
|   | ||||
| @@ -248,7 +248,6 @@ std::ostream & operator<<(std::ostream & os, const EActionType actionType) | ||||
| 	static const std::map<EActionType, std::string> actionTypeToString = | ||||
| 	{ | ||||
| 		{EActionType::END_TACTIC_PHASE, "End tactic phase"}, | ||||
| 		{EActionType::INVALID, "Invalid"}, | ||||
| 		{EActionType::NO_ACTION, "No action"}, | ||||
| 		{EActionType::HERO_SPELL, "Hero spell"}, | ||||
| 		{EActionType::WALK, "Walk"}, | ||||
|   | ||||
| @@ -1000,18 +1000,19 @@ namespace Date | ||||
|  | ||||
| enum class EActionType : int32_t | ||||
| { | ||||
| 	CANCEL = -3, | ||||
| 	END_TACTIC_PHASE = -2, | ||||
| 	INVALID = -1, | ||||
| 	NO_ACTION = 0, | ||||
| 	HERO_SPELL, | ||||
| 	WALK, | ||||
| 	DEFEND, | ||||
| 	NO_ACTION, | ||||
|  | ||||
| 	END_TACTIC_PHASE, | ||||
| 	RETREAT, | ||||
| 	SURRENDER, | ||||
|  | ||||
| 	HERO_SPELL, | ||||
|  | ||||
| 	WALK, | ||||
| 	WAIT, | ||||
| 	DEFEND, | ||||
| 	WALK_AND_ATTACK, | ||||
| 	SHOOT, | ||||
| 	WAIT, | ||||
| 	CATAPULT, | ||||
| 	MONSTER_SPELL, | ||||
| 	BAD_MORALE, | ||||
|   | ||||
| @@ -134,7 +134,6 @@ public: | ||||
| 	virtual void visitBuildBoat(BuildBoat & pack) {} | ||||
| 	virtual void visitQueryReply(QueryReply & pack) {} | ||||
| 	virtual void visitMakeAction(MakeAction & pack) {} | ||||
| 	virtual void visitMakeCustomAction(MakeCustomAction & pack) {} | ||||
| 	virtual void visitDigWithHero(DigWithHero & pack) {} | ||||
| 	virtual void visitCastAdvSpell(CastAdvSpell & pack) {} | ||||
| 	virtual void visitSaveGame(SaveGame & pack) {} | ||||
|   | ||||
| @@ -2513,24 +2513,6 @@ struct DLL_LINKAGE MakeAction : public CPackForServer | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| struct DLL_LINKAGE MakeCustomAction : public CPackForServer | ||||
| { | ||||
| 	MakeCustomAction() = default; | ||||
| 	MakeCustomAction(BattleAction BA) | ||||
| 		: ba(std::move(BA)) | ||||
| 	{ | ||||
| 	} | ||||
| 	BattleAction ba; | ||||
|  | ||||
| 	virtual void visitTyped(ICPackVisitor & visitor) override; | ||||
|  | ||||
| 	template <typename Handler> void serialize(Handler & h, const int version) | ||||
| 	{ | ||||
| 		h & static_cast<CPackForServer &>(*this); | ||||
| 		h & ba; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| struct DLL_LINKAGE DigWithHero : public CPackForServer | ||||
| { | ||||
| 	ObjectInstanceID id; //digging hero id | ||||
|   | ||||
| @@ -638,11 +638,6 @@ void MakeAction::visitTyped(ICPackVisitor & visitor) | ||||
| 	visitor.visitMakeAction(*this); | ||||
| } | ||||
|  | ||||
| void MakeCustomAction::visitTyped(ICPackVisitor & visitor) | ||||
| { | ||||
| 	visitor.visitMakeCustomAction(*this); | ||||
| } | ||||
|  | ||||
| void DigWithHero::visitTyped(ICPackVisitor & visitor) | ||||
| { | ||||
| 	visitor.visitDigWithHero(*this); | ||||
|   | ||||
| @@ -20,7 +20,7 @@ static const int32_t INVALID_UNIT_ID = -1000; | ||||
| BattleAction::BattleAction(): | ||||
| 	side(-1), | ||||
| 	stackNumber(-1), | ||||
| 	actionType(EActionType::INVALID), | ||||
| 	actionType(EActionType::NO_ACTION), | ||||
| 	actionSubtype(-1) | ||||
| { | ||||
| } | ||||
|   | ||||
| @@ -352,7 +352,6 @@ void registerTypesServerPacks(Serializer &s) | ||||
| 	s.template registerType<CPackForServer, BuildBoat>(); | ||||
| 	s.template registerType<CPackForServer, QueryReply>(); | ||||
| 	s.template registerType<CPackForServer, MakeAction>(); | ||||
| 	s.template registerType<CPackForServer, MakeCustomAction>(); | ||||
| 	s.template registerType<CPackForServer, DigWithHero>(); | ||||
| 	s.template registerType<CPackForServer, CastAdvSpell>(); | ||||
| 	s.template registerType<CPackForServer, CastleTeleportHero>(); | ||||
|   | ||||
| @@ -288,14 +288,6 @@ void ApplyGhNetPackVisitor::visitMakeAction(MakeAction & pack) | ||||
| 	result = gh.battles->makeBattleAction(pack.player, pack.ba); | ||||
| } | ||||
|  | ||||
| void ApplyGhNetPackVisitor::visitMakeCustomAction(MakeCustomAction & pack) | ||||
| { | ||||
| 	if (!gh.hasPlayerAt(pack.player, pack.c)) | ||||
| 		gh.throwAndComplain(&pack, "No such pack.player!"); | ||||
|  | ||||
| 	result = gh.battles->makeCustomAction(pack.player, pack.ba); | ||||
| } | ||||
|  | ||||
| void ApplyGhNetPackVisitor::visitDigWithHero(DigWithHero & pack) | ||||
| { | ||||
| 	gh.throwOnWrongOwner(&pack, pack.id); | ||||
|   | ||||
| @@ -55,8 +55,7 @@ public: | ||||
| 	virtual void visitBuildBoat(BuildBoat & pack) override; | ||||
| 	virtual void visitQueryReply(QueryReply & pack) override; | ||||
| 	virtual void visitMakeAction(MakeAction & pack) override; | ||||
| 	virtual void visitMakeCustomAction(MakeCustomAction & pack) override; | ||||
| 	virtual void visitDigWithHero(DigWithHero & pack) override; | ||||
| 	virtual void visitCastAdvSpell(CastAdvSpell & pack) override; | ||||
| 	virtual void visitPlayerMessage(PlayerMessage & pack) override; | ||||
| }; | ||||
| }; | ||||
|   | ||||
| @@ -51,484 +51,530 @@ void BattleActionProcessor::setGameHandler(CGameHandler * newGameHandler) | ||||
| 	gameHandler = newGameHandler; | ||||
| } | ||||
|  | ||||
| bool BattleActionProcessor::makeBattleAction(BattleAction &ba) | ||||
| bool BattleActionProcessor::doEmptyAction(const BattleAction & ba) | ||||
| { | ||||
| 	bool ok = true; | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool BattleActionProcessor::doEndTacticsAction(const BattleAction & ba) | ||||
| { | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool BattleActionProcessor::doWaitAction(const BattleAction & ba) | ||||
| { | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool BattleActionProcessor::doBadMoraleAction(const BattleAction & ba) | ||||
| { | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool BattleActionProcessor::doRetreatAction(const BattleAction & ba) | ||||
| { | ||||
| 	if (!gameHandler->gameState()->curB->battleCanFlee(gameHandler->gameState()->curB->sides.at(ba.side).color)) | ||||
| 	{ | ||||
| 		gameHandler->complain("Cannot retreat!"); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	owner->setBattleResult(EBattleResult::ESCAPE, !ba.side); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool BattleActionProcessor::doSurrenderAction(const BattleAction & ba) | ||||
| { | ||||
| 	PlayerColor player = gameHandler->gameState()->curB->sides.at(ba.side).color; | ||||
| 	int cost = gameHandler->gameState()->curB->battleGetSurrenderCost(player); | ||||
| 	if (cost < 0) | ||||
| 	{ | ||||
| 		gameHandler->complain("Cannot surrender!"); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	if (gameHandler->getResource(player, EGameResID::GOLD) < cost) | ||||
| 	{ | ||||
| 		gameHandler->complain("Not enough gold to surrender!"); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	gameHandler->giveResource(player, EGameResID::GOLD, -cost); | ||||
| 	owner->setBattleResult(EBattleResult::SURRENDER, !ba.side); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool BattleActionProcessor::doHeroSpellAction(const BattleAction & ba) | ||||
| { | ||||
| 	const CGHeroInstance *h = gameHandler->gameState()->curB->battleGetFightingHero(ba.side); | ||||
| 	if (!h) | ||||
| 	{ | ||||
| 		logGlobal->error("Wrong caster!"); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	const CSpell * s = SpellID(ba.actionSubtype).toSpell(); | ||||
| 	if (!s) | ||||
| 	{ | ||||
| 		logGlobal->error("Wrong spell id (%d)!", ba.actionSubtype); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	spells::BattleCast parameters(gameHandler->gameState()->curB, h, spells::Mode::HERO, s); | ||||
|  | ||||
| 	spells::detail::ProblemImpl problem; | ||||
|  | ||||
| 	auto m = s->battleMechanics(¶meters); | ||||
|  | ||||
| 	if(!m->canBeCast(problem))//todo: should we check aimed cast? | ||||
| 	{ | ||||
| 		logGlobal->warn("Spell cannot be cast!"); | ||||
| 		std::vector<std::string> texts; | ||||
| 		problem.getAll(texts); | ||||
| 		for(auto s : texts) | ||||
| 			logGlobal->warn(s); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	StartAction start_action(ba); | ||||
| 	gameHandler->sendAndApply(&start_action); //start spell casting | ||||
|  | ||||
| 	parameters.cast(gameHandler->spellEnv, ba.getTarget(gameHandler->gameState()->curB)); | ||||
|  | ||||
| 	EndAction end_action; | ||||
| 	gameHandler->sendAndApply(&end_action); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool BattleActionProcessor::doWalkAction(const BattleAction & ba) | ||||
| { | ||||
| 	const CStack * stack = gameHandler->battleGetStackByID(ba.stackNumber); | ||||
| 	battle::Target target = ba.getTarget(gameHandler->gameState()->curB); | ||||
|  | ||||
| 	const CStack * stack = gameHandler->battleGetStackByID(ba.stackNumber); //may be nullptr if action is not about stack | ||||
| 	if (!canStackAct(stack)) | ||||
| 		return false; | ||||
|  | ||||
| 	const bool isAboutActiveStack = stack && (ba.stackNumber == gameHandler->gameState()->curB->getActiveStackID()); | ||||
|  | ||||
| 	logGlobal->trace("Making action: %s", ba.toString()); | ||||
|  | ||||
| 	switch(ba.actionType) | ||||
| 	if(target.size() < 1) | ||||
| 	{ | ||||
| 	case EActionType::WALK: //walk | ||||
| 	case EActionType::DEFEND: //defend | ||||
| 	case EActionType::WAIT: //wait | ||||
| 	case EActionType::WALK_AND_ATTACK: //walk or attack | ||||
| 	case EActionType::SHOOT: //shoot | ||||
| 	case EActionType::CATAPULT: //catapult | ||||
| 	case EActionType::STACK_HEAL: //healing with First Aid Tent | ||||
| 	case EActionType::MONSTER_SPELL: | ||||
| 		gameHandler->complain("Destination required for move action."); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 		if (!stack) | ||||
| 	int walkedTiles = moveStack(ba.stackNumber, target.at(0).hexValue); //move | ||||
| 	if (!walkedTiles) | ||||
| 	{ | ||||
| 		gameHandler->complain("Stack failed movement!"); | ||||
| 		return false; | ||||
| 	} | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool BattleActionProcessor::doDefendAction(const BattleAction & ba) | ||||
| { | ||||
| 	const CStack * stack = gameHandler->battleGetStackByID(ba.stackNumber); | ||||
|  | ||||
| 	if (!canStackAct(stack)) | ||||
| 		return false; | ||||
|  | ||||
| 	//defensive stance, TODO: filter out spell boosts from bonus (stone skin etc.) | ||||
| 	SetStackEffect sse; | ||||
| 	Bonus defenseBonusToAdd(BonusDuration::STACK_GETS_TURN, BonusType::PRIMARY_SKILL, BonusSource::OTHER, 20, -1, PrimarySkill::DEFENSE, BonusValueType::PERCENT_TO_ALL); | ||||
| 	Bonus bonus2(BonusDuration::STACK_GETS_TURN, BonusType::PRIMARY_SKILL, BonusSource::OTHER, stack->valOfBonuses(BonusType::DEFENSIVE_STANCE), -1, PrimarySkill::DEFENSE, BonusValueType::ADDITIVE_VALUE); | ||||
| 	Bonus alternativeWeakCreatureBonus(BonusDuration::STACK_GETS_TURN, BonusType::PRIMARY_SKILL, BonusSource::OTHER, 1, -1, PrimarySkill::DEFENSE, BonusValueType::ADDITIVE_VALUE); | ||||
|  | ||||
| 	BonusList defence = *stack->getBonuses(Selector::typeSubtype(BonusType::PRIMARY_SKILL, PrimarySkill::DEFENSE)); | ||||
| 	int oldDefenceValue = defence.totalValue(); | ||||
|  | ||||
| 	defence.push_back(std::make_shared<Bonus>(defenseBonusToAdd)); | ||||
| 	defence.push_back(std::make_shared<Bonus>(bonus2)); | ||||
|  | ||||
| 	int difference = defence.totalValue() - oldDefenceValue; | ||||
| 	std::vector<Bonus> buffer; | ||||
| 	if(difference == 0) //give replacement bonus for creatures not reaching 5 defense points (20% of def becomes 0) | ||||
| 	{ | ||||
| 		difference = 1; | ||||
| 		buffer.push_back(alternativeWeakCreatureBonus); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		buffer.push_back(defenseBonusToAdd); | ||||
| 	} | ||||
|  | ||||
| 	buffer.push_back(bonus2); | ||||
|  | ||||
| 	sse.toUpdate.push_back(std::make_pair(ba.stackNumber, buffer)); | ||||
| 	gameHandler->sendAndApply(&sse); | ||||
|  | ||||
| 	BattleLogMessage message; | ||||
|  | ||||
| 	MetaString text; | ||||
| 	stack->addText(text, EMetaText::GENERAL_TXT, 120); | ||||
| 	stack->addNameReplacement(text); | ||||
| 	text.replaceNumber(difference); | ||||
|  | ||||
| 	message.lines.push_back(text); | ||||
|  | ||||
| 	gameHandler->sendAndApply(&message); | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool BattleActionProcessor::doAttackAction(const BattleAction & ba) | ||||
| { | ||||
| 	const CStack * stack = gameHandler->battleGetStackByID(ba.stackNumber); | ||||
| 	battle::Target target = ba.getTarget(gameHandler->gameState()->curB); | ||||
|  | ||||
| 	if (!canStackAct(stack)) | ||||
| 		return false; | ||||
|  | ||||
| 	if(target.size() < 2) | ||||
| 	{ | ||||
| 		gameHandler->complain("Two destinations required for attack action."); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	BattleHex attackPos = target.at(0).hexValue; | ||||
| 	BattleHex destinationTile = target.at(1).hexValue; | ||||
| 	const CStack * destinationStack = gameHandler->gameState()->curB->battleGetStackByPos(destinationTile, true); | ||||
|  | ||||
| 	if(!destinationStack) | ||||
| 	{ | ||||
| 		gameHandler->complain("Invalid target to attack"); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	BattleHex startingPos = stack->getPosition(); | ||||
| 	int distance = moveStack(ba.stackNumber, attackPos); | ||||
|  | ||||
| 	logGlobal->trace("%s will attack %s", stack->nodeName(), destinationStack->nodeName()); | ||||
|  | ||||
| 	if(stack->getPosition() != attackPos && !(stack->doubleWide() && (stack->getPosition() == attackPos.cloneInDirection(stack->destShiftDir(), false))) ) | ||||
| 	{ | ||||
| 		// we were not able to reach destination tile, nor occupy specified hex | ||||
| 		// abort attack attempt, but treat this case as legal - we may have stepped onto a quicksands/mine | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	if(destinationStack && stack->unitId() == destinationStack->unitId()) //we should just move, it will be handled by following check | ||||
| 	{ | ||||
| 		destinationStack = nullptr; | ||||
| 	} | ||||
|  | ||||
| 	if(!destinationStack) | ||||
| 	{ | ||||
| 		gameHandler->complain("Unit can not attack itself"); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	if(!CStack::isMeleeAttackPossible(stack, destinationStack)) | ||||
| 	{ | ||||
| 		gameHandler->complain("Attack cannot be performed!"); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	//attack | ||||
| 	int totalAttacks = stack->totalAttacks.getMeleeValue(); | ||||
|  | ||||
| 	//TODO: move to CUnitState | ||||
| 	const auto * attackingHero = gameHandler->gameState()->curB->battleGetFightingHero(ba.side); | ||||
| 	if(attackingHero) | ||||
| 	{ | ||||
| 		totalAttacks += attackingHero->valOfBonuses(BonusType::HERO_GRANTS_ATTACKS, stack->creatureIndex()); | ||||
| 	} | ||||
|  | ||||
| 	const bool firstStrike = destinationStack->hasBonusOfType(BonusType::FIRST_STRIKE); | ||||
| 	const bool retaliation = destinationStack->ableToRetaliate(); | ||||
| 	for (int i = 0; i < totalAttacks; ++i) | ||||
| 	{ | ||||
| 		//first strike | ||||
| 		if(i == 0 && firstStrike && retaliation) | ||||
| 		{ | ||||
| 			gameHandler->complain("No such stack!"); | ||||
| 			return false; | ||||
| 		} | ||||
| 		if (!stack->alive()) | ||||
| 		{ | ||||
| 			gameHandler->complain("This stack is dead: " + stack->nodeName()); | ||||
| 			return false; | ||||
| 			makeAttack(destinationStack, stack, 0, stack->getPosition(), true, false, true); | ||||
| 		} | ||||
|  | ||||
| 		if (gameHandler->battleTacticDist()) | ||||
| 		//move can cause death, eg. by walking into the moat, first strike can cause death or paralysis/petrification | ||||
| 		if(stack->alive() && !stack->hasBonusOfType(BonusType::NOT_ACTIVE) && destinationStack->alive()) | ||||
| 		{ | ||||
| 			if (stack && stack->unitSide() != gameHandler->battleGetTacticsSide()) | ||||
| 			{ | ||||
| 				gameHandler->complain("This is not a stack of side that has tactics!"); | ||||
| 				return false; | ||||
| 			} | ||||
| 			makeAttack(stack, destinationStack, (i ? 0 : distance), destinationTile, i==0, false, false);//no distance travelled on second attack | ||||
| 		} | ||||
| 		else if (!isAboutActiveStack) | ||||
|  | ||||
| 		//counterattack | ||||
| 		//we check retaliation twice, so if it unblocked during attack it will work only on next attack | ||||
| 		if(stack->alive() | ||||
| 			&& !stack->hasBonusOfType(BonusType::BLOCKS_RETALIATION) | ||||
| 			&& (i == 0 && !firstStrike) | ||||
| 			&& retaliation && destinationStack->ableToRetaliate()) | ||||
| 		{ | ||||
| 			gameHandler->complain("Action has to be about active stack!"); | ||||
| 			return false; | ||||
| 			makeAttack(destinationStack, stack, 0, stack->getPosition(), true, false, true); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	static EndAction end_action; | ||||
| 	auto wrapAction = [this](BattleAction &ba) | ||||
| 	//return | ||||
| 	if(stack->hasBonusOfType(BonusType::RETURN_AFTER_STRIKE) | ||||
| 		&& target.size() == 3 | ||||
| 		&& startingPos != stack->getPosition() | ||||
| 		&& startingPos == target.at(2).hexValue | ||||
| 		&& stack->alive()) | ||||
| 	{ | ||||
| 		StartAction startAction(ba); | ||||
| 		gameHandler->sendAndApply(&startAction); | ||||
| 		moveStack(ba.stackNumber, startingPos); | ||||
| 		//NOTE: curStack->unitId() == ba.stackNumber (rev 1431) | ||||
| 	} | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| 		return vstd::makeScopeGuard([&]() | ||||
| bool BattleActionProcessor::doShootAction(const BattleAction & ba) | ||||
| { | ||||
| 	const CStack * stack = gameHandler->battleGetStackByID(ba.stackNumber); | ||||
| 	battle::Target target = ba.getTarget(gameHandler->gameState()->curB); | ||||
|  | ||||
| 	if (!canStackAct(stack)) | ||||
| 		return false; | ||||
|  | ||||
| 	if(target.size() < 1) | ||||
| 	{ | ||||
| 		gameHandler->complain("Destination required for shot action."); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	auto destination = target.at(0).hexValue; | ||||
|  | ||||
| 	const CStack * destinationStack = gameHandler->gameState()->curB->battleGetStackByPos(destination); | ||||
|  | ||||
| 	if (!gameHandler->gameState()->curB->battleCanShoot(stack, destination)) | ||||
| 	{ | ||||
| 		gameHandler->complain("Cannot shoot!"); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	if (!destinationStack) | ||||
| 	{ | ||||
| 		gameHandler->complain("No target to shoot!"); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	makeAttack(stack, destinationStack, 0, destination, true, true, false); | ||||
|  | ||||
| 	//ranged counterattack | ||||
| 	if (destinationStack->hasBonusOfType(BonusType::RANGED_RETALIATION) | ||||
| 		&& !stack->hasBonusOfType(BonusType::BLOCKS_RANGED_RETALIATION) | ||||
| 		&& destinationStack->ableToRetaliate() | ||||
| 		&& gameHandler->gameState()->curB->battleCanShoot(destinationStack, stack->getPosition()) | ||||
| 		&& stack->alive()) //attacker may have died (fire shield) | ||||
| 	{ | ||||
| 		makeAttack(destinationStack, stack, 0, stack->getPosition(), true, true, true); | ||||
| 	} | ||||
| 	//allow more than one additional attack | ||||
|  | ||||
| 	int totalRangedAttacks = stack->totalAttacks.getRangedValue(); | ||||
|  | ||||
| 	//TODO: move to CUnitState | ||||
| 	const auto * attackingHero = gameHandler->gameState()->curB->battleGetFightingHero(ba.side); | ||||
| 	if(attackingHero) | ||||
| 	{ | ||||
| 		totalRangedAttacks += attackingHero->valOfBonuses(BonusType::HERO_GRANTS_ATTACKS, stack->creatureIndex()); | ||||
| 	} | ||||
|  | ||||
| 	for(int i = 1; i < totalRangedAttacks; ++i) | ||||
| 	{ | ||||
| 		if( | ||||
| 			stack->alive() | ||||
| 			&& destinationStack->alive() | ||||
| 			&& stack->shots.canUse() | ||||
| 			) | ||||
| 		{ | ||||
| 			gameHandler->sendAndApply(&end_action); | ||||
| 		}); | ||||
| 	}; | ||||
| 			makeAttack(stack, destinationStack, 0, destination, false, true, false); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool BattleActionProcessor::doCatapultAction(const BattleAction & ba) | ||||
| { | ||||
| 	const CStack * stack = gameHandler->gameState()->curB->battleGetStackByID(ba.stackNumber); | ||||
| 	battle::Target target = ba.getTarget(gameHandler->gameState()->curB); | ||||
|  | ||||
| 	if (!canStackAct(stack)) | ||||
| 		return false; | ||||
|  | ||||
| 	std::shared_ptr<const Bonus> catapultAbility = stack->getBonusLocalFirst(Selector::type()(BonusType::CATAPULT)); | ||||
| 	if(!catapultAbility || catapultAbility->subtype < 0) | ||||
| 	{ | ||||
| 		gameHandler->complain("We do not know how to shoot :P"); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		const CSpell * spell = SpellID(catapultAbility->subtype).toSpell(); | ||||
| 		spells::BattleCast parameters(gameHandler->gameState()->curB, stack, spells::Mode::SPELL_LIKE_ATTACK, spell); //We can shot infinitely by catapult | ||||
| 		auto shotLevel = stack->valOfBonuses(Selector::typeSubtype(BonusType::CATAPULT_EXTRA_SHOTS, catapultAbility->subtype)); | ||||
| 		parameters.setSpellLevel(shotLevel); | ||||
| 		parameters.cast(gameHandler->spellEnv, target); | ||||
| 	} | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool BattleActionProcessor::doUnitSpellAction(const BattleAction & ba) | ||||
| { | ||||
| 	const CStack * stack = gameHandler->gameState()->curB->battleGetStackByID(ba.stackNumber); | ||||
| 	battle::Target target = ba.getTarget(gameHandler->gameState()->curB); | ||||
| 	SpellID spellID = SpellID(ba.actionSubtype); | ||||
|  | ||||
| 	if (!canStackAct(stack)) | ||||
| 		return false; | ||||
|  | ||||
| 	std::shared_ptr<const Bonus> randSpellcaster = stack->getBonus(Selector::type()(BonusType::RANDOM_SPELLCASTER)); | ||||
| 	std::shared_ptr<const Bonus> spellcaster = stack->getBonus(Selector::typeSubtype(BonusType::SPELLCASTER, spellID)); | ||||
|  | ||||
| 	//TODO special bonus for genies ability | ||||
| 	if (randSpellcaster && gameHandler->battleGetRandomStackSpell(gameHandler->getRandomGenerator(), stack, CBattleInfoCallback::RANDOM_AIMED) < 0) | ||||
| 		spellID = gameHandler->battleGetRandomStackSpell(gameHandler->getRandomGenerator(), stack, CBattleInfoCallback::RANDOM_GENIE); | ||||
|  | ||||
| 	if (spellID < 0) | ||||
| 		gameHandler->complain("That stack can't cast spells!"); | ||||
| 	else | ||||
| 	{ | ||||
| 		const CSpell * spell = SpellID(spellID).toSpell(); | ||||
| 		spells::BattleCast parameters(gameHandler->gameState()->curB, stack, spells::Mode::CREATURE_ACTIVE, spell); | ||||
| 		int32_t spellLvl = 0; | ||||
| 		if(spellcaster) | ||||
| 			vstd::amax(spellLvl, spellcaster->val); | ||||
| 		if(randSpellcaster) | ||||
| 			vstd::amax(spellLvl, randSpellcaster->val); | ||||
| 		parameters.setSpellLevel(spellLvl); | ||||
| 		parameters.cast(gameHandler->spellEnv, target); | ||||
| 	} | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool BattleActionProcessor::doHealAction(const BattleAction & ba) | ||||
| { | ||||
| 	const CStack * stack = gameHandler->gameState()->curB->battleGetStackByID(ba.stackNumber); | ||||
| 	battle::Target target = ba.getTarget(gameHandler->gameState()->curB); | ||||
|  | ||||
| 	if (!canStackAct(stack)) | ||||
| 		return false; | ||||
|  | ||||
| 	if(target.size() < 1) | ||||
| 	{ | ||||
| 		gameHandler->complain("Destination required for heal action."); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	const battle::Unit * destStack = nullptr; | ||||
| 	std::shared_ptr<const Bonus> healerAbility = stack->getBonusLocalFirst(Selector::type()(BonusType::HEALER)); | ||||
|  | ||||
| 	if(target.at(0).unitValue) | ||||
| 		destStack = target.at(0).unitValue; | ||||
| 	else | ||||
| 		destStack = gameHandler->gameState()->curB->battleGetUnitByPos(target.at(0).hexValue); | ||||
|  | ||||
| 	if(stack == nullptr || destStack == nullptr || !healerAbility || healerAbility->subtype < 0) | ||||
| 	{ | ||||
| 		gameHandler->complain("There is either no healer, no destination, or healer cannot heal :P"); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		const CSpell * spell = SpellID(healerAbility->subtype).toSpell(); | ||||
| 		spells::BattleCast parameters(gameHandler->gameState()->curB, stack, spells::Mode::SPELL_LIKE_ATTACK, spell); //We can heal infinitely by first aid tent | ||||
| 		auto dest = battle::Destination(destStack, target.at(0).hexValue); | ||||
| 		parameters.setSpellLevel(0); | ||||
| 		parameters.cast(gameHandler->spellEnv, {dest}); | ||||
| 	} | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool BattleActionProcessor::canStackAct(const CStack * stack) | ||||
| { | ||||
| 	const bool isAboutActiveStack = stack->unitId() == gameHandler->gameState()->curB->getActiveStackID(); | ||||
|  | ||||
| 	if (!stack) | ||||
| 	{ | ||||
| 		gameHandler->complain("No such stack!"); | ||||
| 		return false; | ||||
| 	} | ||||
| 	if (!stack->alive()) | ||||
| 	{ | ||||
| 		gameHandler->complain("This stack is dead: " + stack->nodeName()); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	if (gameHandler->battleTacticDist()) | ||||
| 	{ | ||||
| 		if (stack && stack->unitSide() != gameHandler->battleGetTacticsSide()) | ||||
| 		{ | ||||
| 			gameHandler->complain("This is not a stack of side that has tactics!"); | ||||
| 			return false; | ||||
| 		} | ||||
| 	} | ||||
| 	else if (!isAboutActiveStack) | ||||
| 	{ | ||||
| 		gameHandler->complain("Action has to be about active stack!"); | ||||
| 		return false; | ||||
| 	} | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool BattleActionProcessor::dispatchBattleAction(const BattleAction & ba) | ||||
| { | ||||
| 	switch(ba.actionType) | ||||
| 	{ | ||||
| 	case EActionType::END_TACTIC_PHASE: //wait | ||||
| 	case EActionType::BAD_MORALE: | ||||
| 	case EActionType::NO_ACTION: | ||||
| 		{ | ||||
| 			auto wrapper = wrapAction(ba); | ||||
| 			break; | ||||
| 		} | ||||
| 	case EActionType::WALK: | ||||
| 		{ | ||||
| 			auto wrapper = wrapAction(ba); | ||||
| 			if(target.size() < 1) | ||||
| 			{ | ||||
| 				gameHandler->complain("Destination required for move action."); | ||||
| 				ok = false; | ||||
| 				break; | ||||
| 			} | ||||
| 			int walkedTiles = moveStack(ba.stackNumber, target.at(0).hexValue); //move | ||||
| 			if (!walkedTiles) | ||||
| 				gameHandler->complain("Stack failed movement!"); | ||||
| 			break; | ||||
| 		} | ||||
| 	case EActionType::DEFEND: | ||||
| 		{ | ||||
| 			//defensive stance, TODO: filter out spell boosts from bonus (stone skin etc.) | ||||
| 			SetStackEffect sse; | ||||
| 			Bonus defenseBonusToAdd(BonusDuration::STACK_GETS_TURN, BonusType::PRIMARY_SKILL, BonusSource::OTHER, 20, -1, PrimarySkill::DEFENSE, BonusValueType::PERCENT_TO_ALL); | ||||
| 			Bonus bonus2(BonusDuration::STACK_GETS_TURN, BonusType::PRIMARY_SKILL, BonusSource::OTHER, stack->valOfBonuses(BonusType::DEFENSIVE_STANCE), | ||||
| 				 -1, PrimarySkill::DEFENSE, BonusValueType::ADDITIVE_VALUE); | ||||
| 			Bonus alternativeWeakCreatureBonus(BonusDuration::STACK_GETS_TURN, BonusType::PRIMARY_SKILL, BonusSource::OTHER, 1, -1, PrimarySkill::DEFENSE, BonusValueType::ADDITIVE_VALUE); | ||||
|  | ||||
| 			BonusList defence = *stack->getBonuses(Selector::typeSubtype(BonusType::PRIMARY_SKILL, PrimarySkill::DEFENSE)); | ||||
| 			int oldDefenceValue = defence.totalValue(); | ||||
|  | ||||
| 			defence.push_back(std::make_shared<Bonus>(defenseBonusToAdd)); | ||||
| 			defence.push_back(std::make_shared<Bonus>(bonus2)); | ||||
|  | ||||
| 			int difference = defence.totalValue() - oldDefenceValue; | ||||
| 			std::vector<Bonus> buffer; | ||||
| 			if(difference == 0) //give replacement bonus for creatures not reaching 5 defense points (20% of def becomes 0) | ||||
| 			{ | ||||
| 				difference = 1; | ||||
| 				buffer.push_back(alternativeWeakCreatureBonus); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				buffer.push_back(defenseBonusToAdd); | ||||
| 			} | ||||
|  | ||||
| 			buffer.push_back(bonus2); | ||||
|  | ||||
| 			sse.toUpdate.push_back(std::make_pair(ba.stackNumber, buffer)); | ||||
| 			gameHandler->sendAndApply(&sse); | ||||
|  | ||||
| 			BattleLogMessage message; | ||||
|  | ||||
| 			MetaString text; | ||||
| 			stack->addText(text, EMetaText::GENERAL_TXT, 120); | ||||
| 			stack->addNameReplacement(text); | ||||
| 			text.replaceNumber(difference); | ||||
|  | ||||
| 			message.lines.push_back(text); | ||||
|  | ||||
| 			gameHandler->sendAndApply(&message); | ||||
| 			//don't break - we share code with next case | ||||
| 		} | ||||
| 		[[fallthrough]]; | ||||
| 	case EActionType::WAIT: | ||||
| 		{ | ||||
| 			auto wrapper = wrapAction(ba); | ||||
| 			break; | ||||
| 		} | ||||
| 	case EActionType::RETREAT: //retreat/flee | ||||
| 		{ | ||||
| 			if (!gameHandler->gameState()->curB->battleCanFlee(gameHandler->gameState()->curB->sides.at(ba.side).color)) | ||||
| 				gameHandler->complain("Cannot retreat!"); | ||||
| 			else | ||||
| 				owner->setBattleResult(EBattleResult::ESCAPE, !ba.side); //surrendering side loses | ||||
| 			break; | ||||
| 		} | ||||
| 	case EActionType::SURRENDER: | ||||
| 		{ | ||||
| 			PlayerColor player = gameHandler->gameState()->curB->sides.at(ba.side).color; | ||||
| 			int cost = gameHandler->gameState()->curB->battleGetSurrenderCost(player); | ||||
| 			if (cost < 0) | ||||
| 				gameHandler->complain("Cannot surrender!"); | ||||
| 			else if (gameHandler->getResource(player, EGameResID::GOLD) < cost) | ||||
| 				gameHandler->complain("Not enough gold to surrender!"); | ||||
| 			else | ||||
| 			{ | ||||
| 				gameHandler->giveResource(player, EGameResID::GOLD, -cost); | ||||
| 				owner->setBattleResult(EBattleResult::SURRENDER, !ba.side); //surrendering side loses | ||||
| 			} | ||||
| 			break; | ||||
| 		} | ||||
| 	case EActionType::WALK_AND_ATTACK: //walk or attack | ||||
| 		{ | ||||
| 			auto wrapper = wrapAction(ba); | ||||
|  | ||||
| 			if(!stack) | ||||
| 			{ | ||||
| 				gameHandler->complain("No attacker"); | ||||
| 				ok = false; | ||||
| 				break; | ||||
| 			} | ||||
|  | ||||
| 			if(target.size() < 2) | ||||
| 			{ | ||||
| 				gameHandler->complain("Two destinations required for attack action."); | ||||
| 				ok = false; | ||||
| 				break; | ||||
| 			} | ||||
|  | ||||
| 			BattleHex attackPos = target.at(0).hexValue; | ||||
| 			BattleHex destinationTile = target.at(1).hexValue; | ||||
| 			const CStack * destinationStack = gameHandler->gameState()->curB->battleGetStackByPos(destinationTile, true); | ||||
|  | ||||
| 			if(!destinationStack) | ||||
| 			{ | ||||
| 				gameHandler->complain("Invalid target to attack"); | ||||
| 				ok = false; | ||||
| 				break; | ||||
| 			} | ||||
|  | ||||
| 			BattleHex startingPos = stack->getPosition(); | ||||
| 			int distance = moveStack(ba.stackNumber, attackPos); | ||||
|  | ||||
| 			logGlobal->trace("%s will attack %s", stack->nodeName(), destinationStack->nodeName()); | ||||
|  | ||||
| 			if(stack->getPosition() != attackPos | ||||
| 				&& !(stack->doubleWide() && (stack->getPosition() == attackPos.cloneInDirection(stack->destShiftDir(), false))) | ||||
| 				) | ||||
| 			{ | ||||
| 				// we were not able to reach destination tile, nor occupy specified hex | ||||
| 				// abort attack attempt, but treat this case as legal - we may have stepped onto a quicksands/mine | ||||
| 				break; | ||||
| 			} | ||||
|  | ||||
| 			if(destinationStack && stack->unitId() == destinationStack->unitId()) //we should just move, it will be handled by following check | ||||
| 			{ | ||||
| 				destinationStack = nullptr; | ||||
| 			} | ||||
|  | ||||
| 			if(!destinationStack) | ||||
| 			{ | ||||
| 				gameHandler->complain("Unit can not attack itself"); | ||||
| 				ok = false; | ||||
| 				break; | ||||
| 			} | ||||
|  | ||||
| 			if(!CStack::isMeleeAttackPossible(stack, destinationStack)) | ||||
| 			{ | ||||
| 				gameHandler->complain("Attack cannot be performed!"); | ||||
| 				ok = false; | ||||
| 				break; | ||||
| 			} | ||||
|  | ||||
| 			//attack | ||||
| 			int totalAttacks = stack->totalAttacks.getMeleeValue(); | ||||
|  | ||||
| 			//TODO: move to CUnitState | ||||
| 			const auto * attackingHero = gameHandler->gameState()->curB->battleGetFightingHero(ba.side); | ||||
| 			if(attackingHero) | ||||
| 			{ | ||||
| 				totalAttacks += attackingHero->valOfBonuses(BonusType::HERO_GRANTS_ATTACKS, stack->creatureIndex()); | ||||
| 			} | ||||
|  | ||||
|  | ||||
| 			const bool firstStrike = destinationStack->hasBonusOfType(BonusType::FIRST_STRIKE); | ||||
| 			const bool retaliation = destinationStack->ableToRetaliate(); | ||||
| 			for (int i = 0; i < totalAttacks; ++i) | ||||
| 			{ | ||||
| 				//first strike | ||||
| 				if(i == 0 && firstStrike && retaliation) | ||||
| 				{ | ||||
| 					makeAttack(destinationStack, stack, 0, stack->getPosition(), true, false, true); | ||||
| 				} | ||||
|  | ||||
| 				//move can cause death, eg. by walking into the moat, first strike can cause death or paralysis/petrification | ||||
| 				if(stack->alive() && !stack->hasBonusOfType(BonusType::NOT_ACTIVE) && destinationStack->alive()) | ||||
| 				{ | ||||
| 					makeAttack(stack, destinationStack, (i ? 0 : distance), destinationTile, i==0, false, false);//no distance travelled on second attack | ||||
| 				} | ||||
|  | ||||
| 				//counterattack | ||||
| 				//we check retaliation twice, so if it unblocked during attack it will work only on next attack | ||||
| 				if(stack->alive() | ||||
| 					&& !stack->hasBonusOfType(BonusType::BLOCKS_RETALIATION) | ||||
| 					&& (i == 0 && !firstStrike) | ||||
| 					&& retaliation && destinationStack->ableToRetaliate()) | ||||
| 				{ | ||||
| 					makeAttack(destinationStack, stack, 0, stack->getPosition(), true, false, true); | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			//return | ||||
| 			if(stack->hasBonusOfType(BonusType::RETURN_AFTER_STRIKE) | ||||
| 				&& target.size() == 3 | ||||
| 				&& startingPos != stack->getPosition() | ||||
| 				&& startingPos == target.at(2).hexValue | ||||
| 				&& stack->alive()) | ||||
| 			{ | ||||
| 				moveStack(ba.stackNumber, startingPos); | ||||
| 				//NOTE: curStack->unitId() == ba.stackNumber (rev 1431) | ||||
| 			} | ||||
| 			break; | ||||
| 		} | ||||
| 	case EActionType::SHOOT: | ||||
| 		{ | ||||
| 			if(target.size() < 1) | ||||
| 			{ | ||||
| 				gameHandler->complain("Destination required for shot action."); | ||||
| 				ok = false; | ||||
| 				break; | ||||
| 			} | ||||
|  | ||||
| 			auto destination = target.at(0).hexValue; | ||||
|  | ||||
| 			const CStack * destinationStack = gameHandler->gameState()->curB->battleGetStackByPos(destination); | ||||
|  | ||||
| 			if (!gameHandler->gameState()->curB->battleCanShoot(stack, destination)) | ||||
| 			{ | ||||
| 				gameHandler->complain("Cannot shoot!"); | ||||
| 				break; | ||||
| 			} | ||||
| 			if (!destinationStack) | ||||
| 			{ | ||||
| 				gameHandler->complain("No target to shoot!"); | ||||
| 				break; | ||||
| 			} | ||||
|  | ||||
| 			auto wrapper = wrapAction(ba); | ||||
|  | ||||
| 			makeAttack(stack, destinationStack, 0, destination, true, true, false); | ||||
|  | ||||
| 			//ranged counterattack | ||||
| 			if (destinationStack->hasBonusOfType(BonusType::RANGED_RETALIATION) | ||||
| 				&& !stack->hasBonusOfType(BonusType::BLOCKS_RANGED_RETALIATION) | ||||
| 				&& destinationStack->ableToRetaliate() | ||||
| 				&& gameHandler->gameState()->curB->battleCanShoot(destinationStack, stack->getPosition()) | ||||
| 				&& stack->alive()) //attacker may have died (fire shield) | ||||
| 			{ | ||||
| 				makeAttack(destinationStack, stack, 0, stack->getPosition(), true, true, true); | ||||
| 			} | ||||
| 			//allow more than one additional attack | ||||
|  | ||||
| 			int totalRangedAttacks = stack->totalAttacks.getRangedValue(); | ||||
|  | ||||
| 			//TODO: move to CUnitState | ||||
| 			const auto * attackingHero = gameHandler->gameState()->curB->battleGetFightingHero(ba.side); | ||||
| 			if(attackingHero) | ||||
| 			{ | ||||
| 				totalRangedAttacks += attackingHero->valOfBonuses(BonusType::HERO_GRANTS_ATTACKS, stack->creatureIndex()); | ||||
| 			} | ||||
|  | ||||
|  | ||||
| 			for(int i = 1; i < totalRangedAttacks; ++i) | ||||
| 			{ | ||||
| 				if( | ||||
| 					stack->alive() | ||||
| 					&& destinationStack->alive() | ||||
| 					&& stack->shots.canUse() | ||||
| 					) | ||||
| 				{ | ||||
| 					makeAttack(stack, destinationStack, 0, destination, false, true, false); | ||||
| 				} | ||||
| 			} | ||||
| 			break; | ||||
| 		} | ||||
| 	case EActionType::CATAPULT: | ||||
| 		{ | ||||
| 			auto wrapper = wrapAction(ba); | ||||
| 			const CStack * shooter = gameHandler->gameState()->curB->battleGetStackByID(ba.stackNumber); | ||||
| 			std::shared_ptr<const Bonus> catapultAbility = stack->getBonusLocalFirst(Selector::type()(BonusType::CATAPULT)); | ||||
| 			if(!catapultAbility || catapultAbility->subtype < 0) | ||||
| 			{ | ||||
| 				gameHandler->complain("We do not know how to shoot :P"); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				const CSpell * spell = SpellID(catapultAbility->subtype).toSpell(); | ||||
| 				spells::BattleCast parameters(gameHandler->gameState()->curB, shooter, spells::Mode::SPELL_LIKE_ATTACK, spell); //We can shot infinitely by catapult | ||||
| 				auto shotLevel = stack->valOfBonuses(Selector::typeSubtype(BonusType::CATAPULT_EXTRA_SHOTS, catapultAbility->subtype)); | ||||
| 				parameters.setSpellLevel(shotLevel); | ||||
| 				parameters.cast(gameHandler->spellEnv, target); | ||||
| 			} | ||||
| 			//finish by scope guard | ||||
| 			break; | ||||
| 		} | ||||
| 		case EActionType::STACK_HEAL: //healing with First Aid Tent | ||||
| 		{ | ||||
| 			auto wrapper = wrapAction(ba); | ||||
| 			const CStack * healer = gameHandler->gameState()->curB->battleGetStackByID(ba.stackNumber); | ||||
|  | ||||
| 			if(target.size() < 1) | ||||
| 			{ | ||||
| 				gameHandler->complain("Destination required for heal action."); | ||||
| 				ok = false; | ||||
| 				break; | ||||
| 			} | ||||
|  | ||||
| 			const battle::Unit * destStack = nullptr; | ||||
| 			std::shared_ptr<const Bonus> healerAbility = stack->getBonusLocalFirst(Selector::type()(BonusType::HEALER)); | ||||
|  | ||||
| 			if(target.at(0).unitValue) | ||||
| 				destStack = target.at(0).unitValue; | ||||
| 			else | ||||
| 				destStack = gameHandler->gameState()->curB->battleGetUnitByPos(target.at(0).hexValue); | ||||
|  | ||||
| 			if(healer == nullptr || destStack == nullptr || !healerAbility || healerAbility->subtype < 0) | ||||
| 			{ | ||||
| 				gameHandler->complain("There is either no healer, no destination, or healer cannot heal :P"); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				const CSpell * spell = SpellID(healerAbility->subtype).toSpell(); | ||||
| 				spells::BattleCast parameters(gameHandler->gameState()->curB, healer, spells::Mode::SPELL_LIKE_ATTACK, spell); //We can heal infinitely by first aid tent | ||||
| 				auto dest = battle::Destination(destStack, target.at(0).hexValue); | ||||
| 				parameters.setSpellLevel(0); | ||||
| 				parameters.cast(gameHandler->spellEnv, {dest}); | ||||
| 			} | ||||
| 			break; | ||||
| 		} | ||||
| 		case EActionType::NO_ACTION: | ||||
| 			return doEmptyAction(ba); | ||||
| 		case EActionType::END_TACTIC_PHASE: | ||||
| 			return doEndTacticsAction(ba); | ||||
| 		case EActionType::RETREAT: | ||||
| 			return doRetreatAction(ba); | ||||
| 		case EActionType::SURRENDER: | ||||
| 			return doSurrenderAction(ba); | ||||
| 		case EActionType::HERO_SPELL: | ||||
| 			return doHeroSpellAction(ba); | ||||
| 		case EActionType::WALK: | ||||
| 			return doWalkAction(ba); | ||||
| 		case EActionType::WAIT: | ||||
| 			return doWaitAction(ba); | ||||
| 		case EActionType::DEFEND: | ||||
| 			return doDefendAction(ba); | ||||
| 		case EActionType::WALK_AND_ATTACK: | ||||
| 			return doAttackAction(ba); | ||||
| 		case EActionType::SHOOT: | ||||
| 			return doShootAction(ba); | ||||
| 		case EActionType::CATAPULT: | ||||
| 			return doCatapultAction(ba); | ||||
| 		case EActionType::MONSTER_SPELL: | ||||
| 		{ | ||||
| 			auto wrapper = wrapAction(ba); | ||||
|  | ||||
| 			const CStack * stack = gameHandler->gameState()->curB->battleGetStackByID(ba.stackNumber); | ||||
| 			SpellID spellID = SpellID(ba.actionSubtype); | ||||
|  | ||||
| 			std::shared_ptr<const Bonus> randSpellcaster = stack->getBonus(Selector::type()(BonusType::RANDOM_SPELLCASTER)); | ||||
| 			std::shared_ptr<const Bonus> spellcaster = stack->getBonus(Selector::typeSubtype(BonusType::SPELLCASTER, spellID)); | ||||
|  | ||||
| 			//TODO special bonus for genies ability | ||||
| 			if (randSpellcaster && gameHandler->battleGetRandomStackSpell(gameHandler->getRandomGenerator(), stack, CBattleInfoCallback::RANDOM_AIMED) < 0) | ||||
| 				spellID = gameHandler->battleGetRandomStackSpell(gameHandler->getRandomGenerator(), stack, CBattleInfoCallback::RANDOM_GENIE); | ||||
|  | ||||
| 			if (spellID < 0) | ||||
| 				gameHandler->complain("That stack can't cast spells!"); | ||||
| 			else | ||||
| 			{ | ||||
| 				const CSpell * spell = SpellID(spellID).toSpell(); | ||||
| 				spells::BattleCast parameters(gameHandler->gameState()->curB, stack, spells::Mode::CREATURE_ACTIVE, spell); | ||||
| 				int32_t spellLvl = 0; | ||||
| 				if(spellcaster) | ||||
| 					vstd::amax(spellLvl, spellcaster->val); | ||||
| 				if(randSpellcaster) | ||||
| 					vstd::amax(spellLvl, randSpellcaster->val); | ||||
| 				parameters.setSpellLevel(spellLvl); | ||||
| 				parameters.cast(gameHandler->spellEnv, target); | ||||
| 			} | ||||
| 			break; | ||||
| 		} | ||||
| 			return doUnitSpellAction(ba); | ||||
| 		case EActionType::BAD_MORALE: | ||||
| 			return doBadMoraleAction(ba); | ||||
| 		case EActionType::STACK_HEAL: | ||||
| 			return doHealAction(ba); | ||||
| 	} | ||||
| 	gameHandler->complain("Unrecognized action type received!!"); | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| bool BattleActionProcessor::makeBattleAction(const BattleAction &ba) | ||||
| { | ||||
| 	logGlobal->trace("Making action: %s", ba.toString()); | ||||
| 	const CStack * stack = gameHandler->gameState()->curB->battleGetStackByID(ba.stackNumber); | ||||
|  | ||||
| 	StartAction startAction(ba); | ||||
| 	gameHandler->sendAndApply(&startAction); | ||||
|  | ||||
| 	bool result = dispatchBattleAction(ba); | ||||
|  | ||||
| 	EndAction endAction; | ||||
| 	gameHandler->sendAndApply(&endAction); | ||||
|  | ||||
| 	if(ba.actionType == EActionType::WAIT || ba.actionType == EActionType::DEFEND || ba.actionType == EActionType::SHOOT || ba.actionType == EActionType::MONSTER_SPELL) | ||||
| 		gameHandler->handleObstacleTriggersForUnit(*gameHandler->spellEnv, *stack); | ||||
|  | ||||
| 	return ok; | ||||
| } | ||||
|  | ||||
| bool BattleActionProcessor::makeCustomAction(BattleAction & ba) | ||||
| { | ||||
| 	switch(ba.actionType) | ||||
| 	{ | ||||
| 	case EActionType::HERO_SPELL: | ||||
| 		{ | ||||
| 			const CGHeroInstance *h = gameHandler->gameState()->curB->battleGetFightingHero(ba.side); | ||||
| 			if (!h) | ||||
| 			{ | ||||
| 				logGlobal->error("Wrong caster!"); | ||||
| 				return false; | ||||
| 			} | ||||
|  | ||||
| 			const CSpell * s = SpellID(ba.actionSubtype).toSpell(); | ||||
| 			if (!s) | ||||
| 			{ | ||||
| 				logGlobal->error("Wrong spell id (%d)!", ba.actionSubtype); | ||||
| 				return false; | ||||
| 			} | ||||
|  | ||||
| 			spells::BattleCast parameters(gameHandler->gameState()->curB, h, spells::Mode::HERO, s); | ||||
|  | ||||
| 			spells::detail::ProblemImpl problem; | ||||
|  | ||||
| 			auto m = s->battleMechanics(¶meters); | ||||
|  | ||||
| 			if(!m->canBeCast(problem))//todo: should we check aimed cast? | ||||
| 			{ | ||||
| 				logGlobal->warn("Spell cannot be cast!"); | ||||
| 				std::vector<std::string> texts; | ||||
| 				problem.getAll(texts); | ||||
| 				for(auto s : texts) | ||||
| 					logGlobal->warn(s); | ||||
| 				return false; | ||||
| 			} | ||||
|  | ||||
| 			StartAction start_action(ba); | ||||
| 			gameHandler->sendAndApply(&start_action); //start spell casting | ||||
|  | ||||
| 			parameters.cast(gameHandler->spellEnv, ba.getTarget(gameHandler->gameState()->curB)); | ||||
|  | ||||
| 			EndAction end_action; | ||||
| 			gameHandler->sendAndApply(&end_action); | ||||
| 			return true; | ||||
| 		} | ||||
| 	} | ||||
| 	return false; | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| int BattleActionProcessor::moveStack(int stack, BattleHex dest) | ||||
| { | ||||
| 	int ret = 0; | ||||
|  | ||||
| 	const CStack *curStack = gameHandler->battleGetStackByID(stack), | ||||
| 		*stackAtEnd = gameHandler->gameState()->curB->battleGetStackByPos(dest); | ||||
| 	const CStack *curStack = gameHandler->battleGetStackByID(stack); | ||||
| 	const CStack *stackAtEnd = gameHandler->gameState()->curB->battleGetStackByPos(dest); | ||||
|  | ||||
| 	assert(curStack); | ||||
| 	assert(dest < GameConstants::BFIELD_SIZE); | ||||
|   | ||||
| @@ -13,7 +13,6 @@ VCMI_LIB_NAMESPACE_BEGIN | ||||
|  | ||||
| struct BattleLogMessage; | ||||
| struct BattleAttack; | ||||
| class BattleProcessor; | ||||
| class BattleAction; | ||||
| struct BattleHex; | ||||
| class CStack; | ||||
| @@ -27,6 +26,7 @@ class CUnitState; | ||||
| VCMI_LIB_NAMESPACE_END | ||||
|  | ||||
| class CGameHandler; | ||||
| class BattleProcessor; | ||||
|  | ||||
| class BattleActionProcessor : boost::noncopyable | ||||
| { | ||||
| @@ -48,11 +48,30 @@ class BattleActionProcessor : boost::noncopyable | ||||
| 	void sendGenericKilledLog(const CStack * defender, int32_t killed, bool multiple); | ||||
| 	void addGenericKilledLog(BattleLogMessage & blm, const CStack * defender, int32_t killed, bool multiple); | ||||
|  | ||||
| 	bool canStackAct(const CStack * stack); | ||||
|  | ||||
| 	bool doEmptyAction(const BattleAction & ba); | ||||
| 	bool doEndTacticsAction(const BattleAction & ba); | ||||
| 	bool doRetreatAction(const BattleAction & ba); | ||||
| 	bool doSurrenderAction(const BattleAction & ba); | ||||
| 	bool doHeroSpellAction(const BattleAction & ba); | ||||
| 	bool doWalkAction(const BattleAction & ba); | ||||
| 	bool doWaitAction(const BattleAction & ba); | ||||
| 	bool doDefendAction(const BattleAction & ba); | ||||
| 	bool doAttackAction(const BattleAction & ba); | ||||
| 	bool doShootAction(const BattleAction & ba); | ||||
| 	bool doCatapultAction(const BattleAction & ba); | ||||
| 	bool doUnitSpellAction(const BattleAction & ba); | ||||
| 	bool doBadMoraleAction(const BattleAction & ba); | ||||
| 	bool doHealAction(const BattleAction & ba); | ||||
|  | ||||
| 	bool dispatchBattleAction(const BattleAction & ba); | ||||
|  | ||||
| public: | ||||
| 	BattleActionProcessor(BattleProcessor * owner); | ||||
| 	explicit BattleActionProcessor(BattleProcessor * owner); | ||||
| 	void setGameHandler(CGameHandler * newGameHandler); | ||||
|  | ||||
| 	bool makeBattleAction(BattleAction &ba); | ||||
| 	bool makeCustomAction(BattleAction &ba); | ||||
| 	bool makeBattleAction(const BattleAction &ba); | ||||
|  | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -290,21 +290,35 @@ void BattleFlowProcessor::activateNextStack() | ||||
| 	//TODO: activate next round if next == nullptr | ||||
| 	const auto & curB = *gameHandler->gameState()->curB; | ||||
|  | ||||
| 	const CStack * next = getNextStack(); | ||||
|  | ||||
| 	if (!next) | ||||
| 		return; | ||||
|  | ||||
| 	BattleUnitsChanged removeGhosts; | ||||
|  | ||||
| 	for(auto stack : curB.stacks) | ||||
| 	// Find next stack that requires manual control | ||||
| 	for (;;) | ||||
| 	{ | ||||
| 		if(stack->ghostPending) | ||||
| 			removeGhosts.changedStacks.emplace_back(stack->unitId(), UnitChanges::EOperation::REMOVE); | ||||
| 	} | ||||
| 		const CStack * next = getNextStack(); | ||||
|  | ||||
| 	if(!removeGhosts.changedStacks.empty()) | ||||
| 		gameHandler->sendAndApply(&removeGhosts); | ||||
| 		if (!next) | ||||
| 			return; | ||||
|  | ||||
| 		BattleUnitsChanged removeGhosts; | ||||
|  | ||||
| 		for(auto stack : curB.stacks) | ||||
| 		{ | ||||
| 			if(stack->ghostPending) | ||||
| 				removeGhosts.changedStacks.emplace_back(stack->unitId(), UnitChanges::EOperation::REMOVE); | ||||
| 		} | ||||
|  | ||||
| 		if(!removeGhosts.changedStacks.empty()) | ||||
| 			gameHandler->sendAndApply(&removeGhosts); | ||||
|  | ||||
| 		if (!tryMakeAutomaticAction(next)) | ||||
| 		{ | ||||
| 			logGlobal->trace("Activating %s", next->nodeName()); | ||||
| 			auto nextId = next->unitId(); | ||||
| 			BattleSetActiveStack sas; | ||||
| 			sas.stack = nextId; | ||||
| 			gameHandler->sendAndApply(&sas); | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| bool BattleFlowProcessor::tryMakeAutomaticAction(const CStack * next) | ||||
| @@ -423,7 +437,7 @@ bool BattleFlowProcessor::tryMakeAutomaticAction(const CStack * next) | ||||
| 			return s->unitOwner() == next->unitOwner() && s->canBeHealed(); | ||||
| 		}); | ||||
|  | ||||
| 		if (!possibleStacks.size()) | ||||
| 		if (possibleStacks.empty()) | ||||
| 		{ | ||||
| 			makeStackDoNothing(next); | ||||
| 			return true; | ||||
| @@ -452,25 +466,11 @@ bool BattleFlowProcessor::tryMakeAutomaticAction(const CStack * next) | ||||
| 		makeStackDoNothing(next); //end immediately if stack was affected by fear | ||||
| 		return true; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		logGlobal->trace("Activating %s", next->nodeName()); | ||||
| 		auto nextId = next->unitId(); | ||||
| 		BattleSetActiveStack sas; | ||||
| 		sas.stack = nextId; | ||||
| 		gameHandler->sendAndApply(&sas); | ||||
| 		return false; | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| void BattleFlowProcessor::onActionMade(const CStack *next) | ||||
| bool BattleFlowProcessor::rollGoodMorale(const CStack * next) | ||||
| { | ||||
| 	//we're after action, all results applied | ||||
| 	owner->checkBattleStateChanges(); //check if this action ended the battle | ||||
|  | ||||
| 	if(next == nullptr) | ||||
| 		return; | ||||
|  | ||||
| 	//check for good morale | ||||
| 	auto nextStackMorale = next->moraleVal(); | ||||
| 	if(    !next->hadMorale | ||||
| @@ -491,11 +491,38 @@ void BattleFlowProcessor::onActionMade(const CStack *next) | ||||
| 			bte.val = 1; | ||||
| 			bte.additionalInfo = 0; | ||||
| 			gameHandler->sendAndApply(&bte); //play animation | ||||
| 			return true; | ||||
| 		} | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| 	if (gameHandler->gameLobby()->state != EServerState::SHUTDOWN) | ||||
| 		owner->endBattle(gameHandler->gameState()->curB->tile, gameHandler->gameState()->curB->battleGetFightingHero(0), gameHandler->gameState()->curB->battleGetFightingHero(1)); | ||||
| void BattleFlowProcessor::onActionMade(const BattleAction &ba) | ||||
| { | ||||
| 	const CStack * next = gameHandler->gameState()->curB->battleGetStackByID(ba.stackNumber); | ||||
|  | ||||
| 	//we're after action, all results applied | ||||
| 	owner->checkBattleStateChanges(); //check if this action ended the battle | ||||
|  | ||||
| 	if(next == nullptr) | ||||
| 		return; | ||||
|  | ||||
| 	bool heroAction = ba.actionType == EActionType::HERO_SPELL || ba.actionType ==EActionType::SURRENDER|| ba.actionType ==EActionType::RETREAT; | ||||
|  | ||||
| 	if (heroAction && next->alive()) | ||||
| 	{ | ||||
| 		// this is action made by hero AND unit is alive (e.g. not killed by casted spell) | ||||
| 		// keep current active stack for next action | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	if (rollGoodMorale(next)) | ||||
| 	{ | ||||
| 		// Good morale - same stack makes 2nd turn | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	activateNextStack(); | ||||
| } | ||||
|  | ||||
| void BattleFlowProcessor::makeStackDoNothing(const CStack * next) | ||||
|   | ||||
| @@ -26,6 +26,7 @@ class BattleFlowProcessor : boost::noncopyable | ||||
|  | ||||
| 	const CStack * getNextStack(); | ||||
|  | ||||
| 	bool rollGoodMorale(const CStack * stack); | ||||
| 	bool tryMakeAutomaticAction(const CStack * stack); | ||||
|  | ||||
| 	void summonGuardiansHelper(std::vector<BattleHex> & output, const BattleHex & targetPosition, ui8 side, bool targetIsTwoHex); | ||||
| @@ -40,10 +41,10 @@ class BattleFlowProcessor : boost::noncopyable | ||||
| 	bool makeAutomaticAction(const CStack *stack, BattleAction &ba); //used when action is taken by stack without volition of player (eg. unguided catapult attack) | ||||
|  | ||||
| public: | ||||
| 	BattleFlowProcessor(BattleProcessor * owner); | ||||
| 	explicit BattleFlowProcessor(BattleProcessor * owner); | ||||
| 	void setGameHandler(CGameHandler * newGameHandler); | ||||
|  | ||||
| 	void onBattleStarted(); | ||||
| 	void onTacticsEnded(); | ||||
| 	void onActionMade(const CStack *stack); | ||||
| 	void onActionMade(const BattleAction &ba); | ||||
| }; | ||||
|   | ||||
| @@ -270,58 +270,20 @@ bool BattleProcessor::makeBattleAction(PlayerColor player, BattleAction &ba) | ||||
| 			return false; | ||||
| 	} | ||||
|  | ||||
| 	return actionsProcessor->makeBattleAction(ba); | ||||
| } | ||||
|  | ||||
| bool BattleProcessor::makeCustomAction(PlayerColor player, BattleAction &ba) | ||||
| { | ||||
| 	const BattleInfo * b = gameHandler->gameState()->curB; | ||||
|  | ||||
| 	if(!b && gameHandler->complain("Can not make action - there is no battle ongoing!")) | ||||
| 		return false; | ||||
|  | ||||
| 	if (ba.side != 0 && ba.side != 1 && gameHandler->complain("Can not make action - invalid battle side!")) | ||||
| 		return false; | ||||
|  | ||||
| 	if(b->tacticDistance) | ||||
| 	{ | ||||
| 		gameHandler->complain("Can not cast spell during tactics mode!"); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	auto active = b->battleActiveUnit(); | ||||
| 	if(!active && gameHandler->complain("No active unit in battle!")) | ||||
| 		return false; | ||||
|  | ||||
| 	auto unitOwner = b->battleGetOwner(active); | ||||
|  | ||||
| 	if(player != unitOwner && gameHandler->complain("Can not make actions in battles you are not part of!")) | ||||
| 		return false; | ||||
|  | ||||
| 	if(ba.actionType != EActionType::HERO_SPELL && gameHandler->complain("Invalid custom action type!")) | ||||
| 		return false; | ||||
|  | ||||
| 	return actionsProcessor->makeCustomAction(ba); | ||||
| 	return makeBattleAction(ba); | ||||
| } | ||||
|  | ||||
| void BattleProcessor::setBattleResult(EBattleResult resultType, int victoriusSide) | ||||
| { | ||||
| 	resultProcessor->setBattleResult(resultType, victoriusSide); | ||||
| 	resultProcessor->endBattle(gameHandler->gameState()->curB->tile, gameHandler->gameState()->curB->battleGetFightingHero(0), gameHandler->gameState()->curB->battleGetFightingHero(1)); | ||||
| } | ||||
|  | ||||
| bool BattleProcessor::makeBattleAction(BattleAction &ba) | ||||
| bool BattleProcessor::makeBattleAction(const BattleAction &ba) | ||||
| { | ||||
| 	return actionsProcessor->makeBattleAction(ba); | ||||
| } | ||||
|  | ||||
| bool BattleProcessor::makeCustomAction(BattleAction &ba) | ||||
| { | ||||
| 	return actionsProcessor->makeCustomAction(ba); | ||||
| } | ||||
|  | ||||
| void BattleProcessor::endBattle(int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2) | ||||
| { | ||||
| 	resultProcessor->endBattle(tile, hero1, hero2); | ||||
| 	bool result = actionsProcessor->makeBattleAction(ba); | ||||
| 	flowProcessor->onActionMade(ba); | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| void BattleProcessor::endBattleConfirm(const BattleInfo * battleInfo) | ||||
|   | ||||
| @@ -44,8 +44,7 @@ class BattleProcessor : boost::noncopyable | ||||
| 	void checkBattleStateChanges(); | ||||
| 	void setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance *heroes[2], bool creatureBank, const CGTownInstance *town); | ||||
|  | ||||
| 	bool makeBattleAction(BattleAction &ba); | ||||
| 	bool makeCustomAction(BattleAction &ba); | ||||
| 	bool makeBattleAction(const BattleAction &ba); | ||||
|  | ||||
| 	void setBattleResult(EBattleResult resultType, int victoriusSide); | ||||
| public: | ||||
| @@ -60,9 +59,7 @@ public: | ||||
| 	void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false); //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle | ||||
|  | ||||
| 	bool makeBattleAction(PlayerColor player, BattleAction &ba); | ||||
| 	bool makeCustomAction(PlayerColor player, BattleAction &ba); | ||||
|  | ||||
| 	void endBattle(int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2); //ends battle | ||||
| 	void endBattleConfirm(const BattleInfo * battleInfo); | ||||
| 	void battleAfterLevelUp(const BattleResult &result); | ||||
|  | ||||
|   | ||||
| @@ -39,8 +39,8 @@ | ||||
| #include "../../lib/spells/Problem.h" | ||||
|  | ||||
| BattleResultProcessor::BattleResultProcessor(BattleProcessor * owner) | ||||
| 	: owner(owner) | ||||
| 	, gameHandler(nullptr) | ||||
| //	: owner(owner) | ||||
| 	: gameHandler(nullptr) | ||||
| { | ||||
| } | ||||
|  | ||||
| @@ -550,5 +550,4 @@ void BattleResultProcessor::setBattleResult(EBattleResult resultType, int victor | ||||
| 	battleResult->result = resultType; | ||||
| 	battleResult->winner = victoriusSide; //surrendering side loses | ||||
| 	gameHandler->gameState()->curB->calculateCasualties(battleResult->casualties); | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -61,14 +61,14 @@ struct FinishingBattleHelper | ||||
|  | ||||
| class BattleResultProcessor : boost::noncopyable | ||||
| { | ||||
| 	BattleProcessor * owner; | ||||
| //	BattleProcessor * owner; | ||||
| 	CGameHandler * gameHandler; | ||||
|  | ||||
| 	std::unique_ptr<BattleResult> battleResult; | ||||
| 	std::unique_ptr<FinishingBattleHelper> finishingBattle; | ||||
|  | ||||
| public: | ||||
| 	BattleResultProcessor(BattleProcessor * owner); | ||||
| 	explicit BattleResultProcessor(BattleProcessor * owner); | ||||
| 	void setGameHandler(CGameHandler * newGameHandler); | ||||
|  | ||||
| 	void setBattleResult(EBattleResult resultType, int victoriusSide); | ||||
|   | ||||
| @@ -43,7 +43,7 @@ CBattleQuery::CBattleQuery(CGameHandler * owner): | ||||
| bool CBattleQuery::blocksPack(const CPack * pack) const | ||||
| { | ||||
| 	const char * name = typeid(*pack).name(); | ||||
| 	return strcmp(name, typeid(MakeAction).name()) && strcmp(name, typeid(MakeCustomAction).name()); | ||||
| 	return strcmp(name, typeid(MakeAction).name()) != 0; | ||||
| } | ||||
|  | ||||
| void CBattleQuery::onRemoval(PlayerColor color) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user