mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Battle Interface refactoring: separated projectile handling into a
separate class (untested)
This commit is contained in:
		| @@ -5,6 +5,9 @@ set(client_SRCS | ||||
| 		battle/CBattleAnimations.cpp | ||||
| 		battle/CBattleInterfaceClasses.cpp | ||||
| 		battle/CBattleInterface.cpp | ||||
| 		battle/CBattleObstacleController.cpp | ||||
| 		battle/CBattleProjectileController.cpp | ||||
| 		battle/CBattleSiegeController.cpp | ||||
| 		battle/CCreatureAnimation.cpp | ||||
|  | ||||
| 		gui/CAnimation.cpp | ||||
| @@ -78,6 +81,9 @@ set(client_HEADERS | ||||
| 		battle/CBattleAnimations.h | ||||
| 		battle/CBattleInterfaceClasses.h | ||||
| 		battle/CBattleInterface.h | ||||
| 		battle/CBattleObstacleController.h | ||||
| 		battle/CBattleProjectileController.h | ||||
| 		battle/CBattleSiegeController.h | ||||
| 		battle/CCreatureAnimation.h | ||||
|  | ||||
| 		gui/CAnimation.h | ||||
|   | ||||
| @@ -14,6 +14,7 @@ | ||||
|  | ||||
| #include "CBattleInterfaceClasses.h" | ||||
| #include "CBattleInterface.h" | ||||
| #include "CBattleProjectileController.h" | ||||
| #include "CCreatureAnimation.h" | ||||
|  | ||||
| #include "../CGameInfo.h" | ||||
| @@ -196,15 +197,9 @@ bool CDefenceAnimation::init() | ||||
| 	} | ||||
| 	//unit reversed | ||||
|  | ||||
| 	if(rangedAttack && attacker != nullptr) //delay hit animation | ||||
| 	if(rangedAttack && attacker != nullptr && owner->projectilesController->hasActiveProjectile(attacker)) //delay hit animation | ||||
| 	{ | ||||
| 		for(std::list<ProjectileInfo>::const_iterator it = owner->projectiles.begin(); it != owner->projectiles.end(); ++it) | ||||
| 		{ | ||||
| 			if(it->creID == attacker->getCreature()->idNumber) | ||||
| 			{ | ||||
| 				return false; | ||||
| 			} | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	// synchronize animation with attacker, unless defending or attacked by shooter: | ||||
| @@ -725,14 +720,10 @@ bool CShootingAnimation::init() | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	// opponent must face attacker ( = different directions) before he can be attacked | ||||
| 	//FIXME: this cause freeze | ||||
|  | ||||
| //	if (attackingStack && attackedStack && | ||||
| //	    owner->creDir[attackingStack->ID] == owner->creDir[attackedStack->ID]) | ||||
| //		return false; | ||||
|  | ||||
| 	// Create the projectile animation | ||||
| 	// opponent must face attacker ( = different directions) before he can be attacked | ||||
| 	//if (attackingStack && attackedStack && owner->creDir[attackingStack->ID] == owner->creDir[attackedStack->ID]) | ||||
| 	//	return false; | ||||
|  | ||||
| 	//maximal angle in radians between straight horizontal line and shooting line for which shot is considered to be straight (absoulte value) | ||||
| 	static const double straightAngle = 0.2; | ||||
| @@ -744,40 +735,31 @@ bool CShootingAnimation::init() | ||||
|  | ||||
| 	if(shooterInfo->idNumber == CreatureID::ARROW_TOWERS) | ||||
| 	{ | ||||
| 		int creID = owner->siegeH->town->town->clientInfo.siegeShooter; | ||||
| 		shooterInfo = CGI->creh->operator[](creID); | ||||
| 		CreatureID creID = owner->siegeH->town->town->clientInfo.siegeShooter; | ||||
| 		shooterInfo = CGI->creh->objects[creID]; | ||||
| 	} | ||||
| 	if(!shooterInfo->animation.missleFrameAngles.size()) | ||||
| 		logAnim->error("Mod error: Creature '%s' on the Archer's tower is not a shooter. Mod should be fixed. Trying to use archer's data instead..." | ||||
| 			, shooterInfo->nameSing); | ||||
|  | ||||
| 	auto & angles = shooterInfo->animation.missleFrameAngles.size() | ||||
| 		? shooterInfo->animation.missleFrameAngles | ||||
| 		: CGI->creh->operator[](CreatureID::ARCHER)->animation.missleFrameAngles; | ||||
|  | ||||
| 	ProjectileInfo spi; | ||||
| 	spi.shotDone = false; | ||||
| 	spi.creID = shooter->getCreature()->idNumber; | ||||
| 	spi.stackID = shooter->ID; | ||||
| 	// reverse if creature is facing right OR this is non-existing stack that is not tower (war machines) | ||||
| 	spi.reverse = attackingStack ? !owner->creDir[attackingStack->ID] : shooter->getCreature()->idNumber != CreatureID::ARROW_TOWERS ; | ||||
|  | ||||
| 	spi.step = 0; | ||||
| 	spi.frameNum = 0; | ||||
|  | ||||
| 	Point fromPos; | ||||
| 	Point shooterPos; | ||||
| 	Point shotPos; | ||||
| 	Point destPos; | ||||
|  | ||||
| 	// NOTE: two lines below return different positions (very notable with 2-hex creatures). Obtaining via creanims seems to be more precise | ||||
| 	fromPos = owner->creAnims[spi.stackID]->pos.topLeft(); | ||||
| 	shooterPos = owner->creAnims[shooter->ID]->pos.topLeft(); | ||||
| 	//xycoord = CClickableHex::getXYUnitAnim(shooter->position, true, shooter, owner); | ||||
|  | ||||
| 	destPos = CClickableHex::getXYUnitAnim(dest, attackedStack, owner); | ||||
| 	destPos = CClickableHex::getXYUnitAnim(dest, attackedStack, owner) + Point(225, 225); | ||||
|  | ||||
| 	// to properly translate coordinates when shooter is rotated | ||||
| 	int multiplier = spi.reverse ? -1 : 1; | ||||
| 	int multiplier = 0; | ||||
| 	if (shooter) | ||||
| 		multiplier = owner->creDir[shooter->ID] ? 1 : -1; | ||||
| 	else | ||||
| 	{ | ||||
| 		assert(false); // unreachable? | ||||
| 		multiplier = shooter->getCreature()->idNumber == CreatureID::ARROW_TOWERS ? -1 : 1; | ||||
| 	} | ||||
|  | ||||
| 	double projectileAngle = atan2(fabs((double)destPos.y - fromPos.y), fabs((double)destPos.x - fromPos.x)); | ||||
| 	double projectileAngle = atan2(fabs((double)destPos.y - shooterPos.y), fabs((double)destPos.x - shooterPos.x)); | ||||
| 	if(shooter->getPosition() < dest) | ||||
| 		projectileAngle = -projectileAngle; | ||||
|  | ||||
| @@ -785,103 +767,23 @@ bool CShootingAnimation::init() | ||||
| 	if (projectileAngle > straightAngle) | ||||
| 	{ | ||||
| 		//upper shot | ||||
| 		spi.x0 = fromPos.x + 222 + ( -25 + shooterInfo->animation.upperRightMissleOffsetX ) * multiplier; | ||||
| 		spi.y0 = fromPos.y + 265 + shooterInfo->animation.upperRightMissleOffsetY; | ||||
| 		shotPos.x = shooterPos.x + 222 + ( -25 + shooterInfo->animation.upperRightMissleOffsetX ) * multiplier; | ||||
| 		shotPos.y = shooterPos.y + 265 + shooterInfo->animation.upperRightMissleOffsetY; | ||||
| 	} | ||||
| 	else if (projectileAngle < -straightAngle) | ||||
| 	{ | ||||
| 		//lower shot | ||||
| 		spi.x0 = fromPos.x + 222 + ( -25 + shooterInfo->animation.lowerRightMissleOffsetX ) * multiplier; | ||||
| 		spi.y0 = fromPos.y + 265 + shooterInfo->animation.lowerRightMissleOffsetY; | ||||
| 		shotPos.x = shooterPos.x + 222 + ( -25 + shooterInfo->animation.lowerRightMissleOffsetX ) * multiplier; | ||||
| 		shotPos.y = shooterPos.y + 265 + shooterInfo->animation.lowerRightMissleOffsetY; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		//straight shot | ||||
| 		spi.x0 = fromPos.x + 222 + ( -25 + shooterInfo->animation.rightMissleOffsetX ) * multiplier; | ||||
| 		spi.y0 = fromPos.y + 265 + shooterInfo->animation.rightMissleOffsetY; | ||||
| 		shotPos.x = shooterPos.x + 222 + ( -25 + shooterInfo->animation.rightMissleOffsetX ) * multiplier; | ||||
| 		shotPos.y = shooterPos.y + 265 + shooterInfo->animation.rightMissleOffsetY; | ||||
| 	} | ||||
|  | ||||
| 	spi.x = spi.x0; | ||||
| 	spi.y = spi.y0; | ||||
|  | ||||
| 	destPos += Point(225, 225); | ||||
|  | ||||
| 	// recalculate angle taking in account offsets | ||||
| 	//projectileAngle = atan2(fabs(destPos.y - spi.y), fabs(destPos.x - spi.x)); | ||||
| 	//if(shooter->position < dest) | ||||
| 	//	projectileAngle = -projectileAngle; | ||||
|  | ||||
| 	if (attackedStack) | ||||
| 	{ | ||||
| 		double animSpeed = AnimationControls::getProjectileSpeed(); // flight speed of projectile | ||||
| 		double distanceSquared = (destPos.x - spi.x) * (destPos.x - spi.x) + (destPos.y - spi.y) * (destPos.y - spi.y); | ||||
| 		double distance = sqrt(distanceSquared); | ||||
| 		spi.lastStep = std::round(distance / animSpeed); | ||||
| 		if(spi.lastStep == 0) | ||||
| 			spi.lastStep = 1; | ||||
| 		spi.dx = (destPos.x - spi.x) / spi.lastStep; | ||||
| 		spi.dy = (destPos.y - spi.y) / spi.lastStep; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		// Catapult attack | ||||
| 		spi.catapultInfo.reset(new CatapultProjectileInfo(Point((int)spi.x, (int)spi.y), destPos)); | ||||
|  | ||||
| 		double animSpeed = AnimationControls::getProjectileSpeed() / 10; | ||||
| 		spi.lastStep = static_cast<int>(std::abs((destPos.x - spi.x) / animSpeed)); | ||||
| 		spi.dx = animSpeed; | ||||
| 		spi.dy = 0; | ||||
|  | ||||
| 		auto img = owner->idToProjectile[spi.creID]->getImage(0); | ||||
|  | ||||
| 		// Add explosion anim | ||||
| 		Point animPos(destPos.x - 126 + img->width() / 2, | ||||
| 					  destPos.y - 105 + img->height() / 2); | ||||
|  | ||||
| 		owner->addNewAnim( new CEffectAnimation(owner, catapultDamage ? "SGEXPL.DEF" : "CSGRCK.DEF", animPos.x, animPos.y)); | ||||
| 	} | ||||
| 	double pi = boost::math::constants::pi<double>(); | ||||
|  | ||||
| 	//in some cases (known one: hero grants shooter bonus to unit) the shooter stack's projectile may not be properly initialized | ||||
| 	if (!owner->idToProjectile.count(spi.creID) && !owner->idToRay.count(spi.creID)) | ||||
| 		owner->initStackProjectile(shooter); | ||||
|  | ||||
| 	if (owner->idToProjectile.count(spi.creID)) | ||||
| 	{ | ||||
| 		// only frames below maxFrame are usable: anything  higher is either no present or we don't know when it should be used | ||||
| 		size_t maxFrame = std::min<size_t>(angles.size(), owner->idToProjectile.at(spi.creID)->size(0)); | ||||
|  | ||||
| 		assert(maxFrame > 0); | ||||
|  | ||||
| 		// values in angles array indicate position from which this frame was rendered, in degrees. | ||||
| 		// find frame that has closest angle to one that we need for this shot | ||||
| 		size_t bestID = 0; | ||||
| 		double bestDiff = fabs( angles[0] / 180 * pi - projectileAngle ); | ||||
|  | ||||
| 		for (size_t i=1; i<maxFrame; i++) | ||||
| 		{ | ||||
| 			double currentDiff = fabs( angles[i] / 180 * pi - projectileAngle ); | ||||
| 			if (currentDiff < bestDiff) | ||||
| 			{ | ||||
| 				bestID = i; | ||||
| 				bestDiff = currentDiff; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		spi.frameNum = static_cast<int>(bestID); | ||||
| 	} | ||||
| 	else if (owner->idToRay.count(spi.creID)) | ||||
| 	{ | ||||
| 		// no-op | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		logGlobal->error("Unable to find valid projectile for shooter %d", spi.creID); | ||||
| 	} | ||||
|  | ||||
| 	// Set projectile animation start delay which is specified in frames | ||||
| 	spi.animStartDelay = shooterInfo->animation.attackClimaxFrame; | ||||
| 	owner->projectiles.push_back(spi); | ||||
| 	owner->projectilesController->createProjectile(attackingStack, attackedStack, shotPos, destPos); | ||||
|  | ||||
| 	//attack animation | ||||
|  | ||||
|   | ||||
| @@ -186,23 +186,6 @@ public: | ||||
| 	virtual ~CReverseAnimation(){}; | ||||
| }; | ||||
|  | ||||
| /// Small struct which contains information about the position and the velocity of a projectile | ||||
| struct ProjectileInfo | ||||
| { | ||||
| 	double x0, y0; //initial position on the screen | ||||
| 	double x, y; //position on the screen | ||||
| 	double dx, dy; //change in position in one step | ||||
| 	int step, lastStep; //to know when finish showing this projectile | ||||
| 	int creID; //ID of creature that shot this projectile | ||||
| 	int stackID; //ID of stack | ||||
| 	int frameNum; //frame to display form projectile animation | ||||
| 	//bool spin; //if true, frameNum will be increased | ||||
| 	int animStartDelay; //frame of shooter animation when projectile should appear | ||||
| 	bool shotDone; // actual shot already done, projectile is flying | ||||
| 	bool reverse; //if true, projectile will be flipped by vertical asix | ||||
| 	std::shared_ptr<CatapultProjectileInfo> catapultInfo; // holds info about the parabolic trajectory of the cannon | ||||
| }; | ||||
|  | ||||
| class CRangedAttackAnimation : public CAttackAnimation | ||||
| { | ||||
| public: | ||||
|   | ||||
| @@ -13,6 +13,7 @@ | ||||
| #include "CBattleAnimations.h" | ||||
| #include "CBattleInterfaceClasses.h" | ||||
| #include "CCreatureAnimation.h" | ||||
| #include "CBattleProjectileController.h" | ||||
|  | ||||
| #include "../CBitmapHandler.h" | ||||
| #include "../CGameInfo.h" | ||||
| @@ -113,6 +114,8 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet | ||||
| { | ||||
| 	OBJ_CONSTRUCTION; | ||||
|  | ||||
| 	projectilesController.reset(new CBattleProjectileController(this)); | ||||
|  | ||||
| 	if(spectatorInt) | ||||
| 	{ | ||||
| 		curInt = spectatorInt; | ||||
| @@ -1008,33 +1011,7 @@ void CBattleInterface::unitAdded(const CStack * stack) | ||||
| 	//loading projectiles for units | ||||
| 	if(stack->isShooter()) | ||||
| 	{ | ||||
| 		initStackProjectile(stack); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void CBattleInterface::initStackProjectile(const CStack * stack) | ||||
| { | ||||
| 	const CCreature * creature;//creature whose shots should be loaded | ||||
| 	if(stack->getCreature()->idNumber == CreatureID::ARROW_TOWERS) | ||||
| 		creature = CGI->creh->objects[siegeH->town->town->clientInfo.siegeShooter]; | ||||
| 	else | ||||
| 		creature = stack->getCreature(); | ||||
|  | ||||
| 	if (creature->animation.projectileRay.empty()) | ||||
| 	{ | ||||
| 		std::shared_ptr<CAnimation> projectile = std::make_shared<CAnimation>(creature->animation.projectileImageName); | ||||
| 		projectile->preload(); | ||||
|  | ||||
| 		if(projectile->size(1) != 0) | ||||
| 			logAnim->error("Expected empty group 1 in stack projectile"); | ||||
| 		else | ||||
| 			projectile->createFlippedGroup(0, 1); | ||||
|  | ||||
| 		idToProjectile[stack->getCreature()->idNumber] = projectile; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		idToRay[stack->getCreature()->idNumber] = creature->animation.projectileRay; | ||||
| 		projectilesController->initStackProjectile(stack); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -2990,35 +2967,6 @@ void CBattleInterface::SiegeHelper::printPartOfWall(SDL_Surface *to, int what) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| CatapultProjectileInfo::CatapultProjectileInfo(Point from, Point dest) | ||||
| { | ||||
| 	facA = 0.005; // seems to be constant | ||||
|  | ||||
| 	// system of 2 linear equations, solutions of which are missing coefficients | ||||
| 	// for quadratic equation a*x*x + b*x + c | ||||
| 	double eq[2][3] = { | ||||
| 		{ static_cast<double>(from.x), 1.0, from.y - facA*from.x*from.x }, | ||||
| 		{ static_cast<double>(dest.x), 1.0, dest.y - facA*dest.x*dest.x } | ||||
| 	}; | ||||
|  | ||||
| 	// solve system via determinants | ||||
| 	double det  = eq[0][0] *eq[1][1] - eq[1][0] *eq[0][1]; | ||||
| 	double detB = eq[0][2] *eq[1][1] - eq[1][2] *eq[0][1]; | ||||
| 	double detC = eq[0][0] *eq[1][2] - eq[1][0] *eq[0][2]; | ||||
|  | ||||
| 	facB = detB / det; | ||||
| 	facC = detC / det; | ||||
|  | ||||
| 	// make sure that parabola is correct e.g. passes through from and dest | ||||
| 	assert(fabs(calculateY(from.x) - from.y) < 1.0); | ||||
| 	assert(fabs(calculateY(dest.x) - dest.y) < 1.0); | ||||
| } | ||||
|  | ||||
| double CatapultProjectileInfo::calculateY(double x) | ||||
| { | ||||
| 	return facA *pow(x, 2.0) + facB *x + facC; | ||||
| } | ||||
|  | ||||
| void CBattleInterface::showAll(SDL_Surface *to) | ||||
| { | ||||
| 	show(to); | ||||
| @@ -3036,7 +2984,7 @@ void CBattleInterface::show(SDL_Surface *to) | ||||
|  | ||||
| 	showBackground(to); | ||||
| 	showBattlefieldObjects(to); | ||||
| 	showProjectiles(to); | ||||
| 	projectilesController->showProjectiles(to); | ||||
|  | ||||
| 	if(battleActionsStarted) | ||||
| 		updateBattleAnimations(); | ||||
| @@ -3191,116 +3139,6 @@ void CBattleInterface::showHighlightedHex(SDL_Surface *to, BattleHex hex, bool d | ||||
| 		CSDL_Ext::blit8bppAlphaTo24bpp(cellBorder, nullptr, to, &temp_rect); //redraw border to make it light green instead of shaded | ||||
| } | ||||
|  | ||||
| void CBattleInterface::showProjectiles(SDL_Surface *to) | ||||
| { | ||||
| 	assert(to); | ||||
|  | ||||
| 	std::list< std::list<ProjectileInfo>::iterator > toBeDeleted; | ||||
| 	for (auto it = projectiles.begin(); it!=projectiles.end(); ++it) | ||||
| 	{ | ||||
| 		// Check if projectile is already visible (shooter animation did the shot) | ||||
| 		if (!it->shotDone) | ||||
| 		{ | ||||
| 			// frame we're waiting for is reached OR animation has already finished | ||||
| 			if (creAnims[it->stackID]->getCurrentFrame() >= it->animStartDelay || | ||||
| 				creAnims[it->stackID]->isShooting() == false) | ||||
| 			{ | ||||
| 				//at this point projectile should become visible | ||||
| 				creAnims[it->stackID]->pause(); // pause animation | ||||
| 				it->shotDone = true; | ||||
| 			} | ||||
| 			else | ||||
| 				continue; // wait... | ||||
| 		} | ||||
|  | ||||
| 		if (idToProjectile.count(it->creID)) | ||||
| 		{ | ||||
| 			size_t group = it->reverse ? 1 : 0; | ||||
| 			auto image = idToProjectile[it->creID]->getImage(it->frameNum, group, true); | ||||
|  | ||||
| 			if(image) | ||||
| 			{ | ||||
| 				SDL_Rect dst; | ||||
| 				dst.h = image->height(); | ||||
| 				dst.w = image->width(); | ||||
| 				dst.x = static_cast<int>(it->x - dst.w / 2); | ||||
| 				dst.y = static_cast<int>(it->y - dst.h / 2); | ||||
|  | ||||
| 				image->draw(to, &dst, nullptr); | ||||
| 			} | ||||
| 		} | ||||
| 		if (idToRay.count(it->creID)) | ||||
| 		{ | ||||
| 			auto const & ray = idToRay[it->creID]; | ||||
|  | ||||
| 			if (std::abs(it->dx) > std::abs(it->dy)) // draw in horizontal axis | ||||
| 			{ | ||||
| 				int y1 =  it->y0 - ray.size() / 2; | ||||
| 				int y2 =  it->y - ray.size() / 2; | ||||
|  | ||||
| 				int x1 = it->x0; | ||||
| 				int x2 = it->x; | ||||
|  | ||||
| 				for (size_t i = 0; i < ray.size(); ++i) | ||||
| 				{ | ||||
| 					SDL_Color beginColor{ ray[i].r1, ray[i].g1, ray[i].b1, ray[i].a1}; | ||||
| 					SDL_Color endColor  { ray[i].r2, ray[i].g2, ray[i].b2, ray[i].a2}; | ||||
|  | ||||
| 					CSDL_Ext::drawLine(to, x1, y1 + i, x2, y2 + i, beginColor, endColor); | ||||
| 				} | ||||
| 			} | ||||
| 			else // draw in vertical axis | ||||
| 			{ | ||||
| 				int x1 = it->x0 - ray.size() / 2; | ||||
| 				int x2 = it->x - ray.size() / 2; | ||||
|  | ||||
| 				int y1 =  it->y0; | ||||
| 				int y2 =  it->y; | ||||
|  | ||||
| 				for (size_t i = 0; i < ray.size(); ++i) | ||||
| 				{ | ||||
| 					SDL_Color beginColor{ ray[i].r1, ray[i].g1, ray[i].b1, ray[i].a1}; | ||||
| 					SDL_Color endColor  { ray[i].r2, ray[i].g2, ray[i].b2, ray[i].a2}; | ||||
|  | ||||
| 					CSDL_Ext::drawLine(to, x1 + i, y1, x2 + i, y2, beginColor, endColor); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Update projectile | ||||
| 		++it->step; | ||||
| 		if (it->step > it->lastStep) | ||||
| 		{ | ||||
| 			toBeDeleted.insert(toBeDeleted.end(), it); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			if (it->catapultInfo) | ||||
| 			{ | ||||
| 				// Parabolic shot of the trajectory, as follows: f(x) = ax^2 + bx + c | ||||
| 				it->x += it->dx; | ||||
| 				it->y = it->catapultInfo->calculateY(it->x); | ||||
|  | ||||
| 				++(it->frameNum); | ||||
| 				it->frameNum %= idToProjectile[it->creID]->size(0); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				// Normal projectile, just add the calculated "deltas" to the x and y positions. | ||||
| 				it->x += it->dx; | ||||
| 				it->y += it->dy; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for (auto & elem : toBeDeleted) | ||||
| 	{ | ||||
| 		// resume animation | ||||
| 		creAnims[elem->stackID]->play(); | ||||
| 		projectiles.erase(elem); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void CBattleInterface::showBattlefieldObjects(SDL_Surface *to) | ||||
| { | ||||
| 	auto showHexEntry = [&](BattleObjectsByHex::HexData & hex) | ||||
|   | ||||
| @@ -47,7 +47,6 @@ class CCallback; | ||||
| class CButton; | ||||
| class CToggleButton; | ||||
| class CToggleGroup; | ||||
| struct CatapultProjectileInfo; | ||||
| class CBattleAnimation; | ||||
| class CBattleHero; | ||||
| class CBattleConsole; | ||||
| @@ -55,11 +54,12 @@ class CBattleResultWindow; | ||||
| class CStackQueue; | ||||
| class CPlayerInterface; | ||||
| class CCreatureAnimation; | ||||
| struct ProjectileInfo; | ||||
| class CClickableHex; | ||||
| class CAnimation; | ||||
| class IImage; | ||||
|  | ||||
| class CBattleProjectileController; | ||||
|  | ||||
| /// Small struct which contains information about the id of the attacked stack, the damage dealt,... | ||||
| struct StackAttackedInfo | ||||
| { | ||||
| @@ -104,16 +104,6 @@ struct BattleObjectsByHex | ||||
| 	std::array<HexData, GameConstants::BFIELD_SIZE> hex; | ||||
| }; | ||||
|  | ||||
| /// Small struct which is needed for drawing the parabolic trajectory of the catapult cannon | ||||
| struct CatapultProjectileInfo | ||||
| { | ||||
| 	CatapultProjectileInfo(Point from, Point dest); | ||||
|  | ||||
| 	double facA, facB, facC; | ||||
|  | ||||
| 	double calculateY(double x); | ||||
| }; | ||||
|  | ||||
| enum class MouseHoveredHexContext | ||||
| { | ||||
| 	UNOCCUPIED_HEX, | ||||
| @@ -148,9 +138,6 @@ private: | ||||
| 	const CGHeroInstance *attackingHeroInstance, *defendingHeroInstance; | ||||
| 	std::map<int32_t, std::shared_ptr<CCreatureAnimation>> creAnims; //animations of creatures from fighting armies (order by BattleInfo's stacks' ID) | ||||
|  | ||||
| 	std::map<int, std::shared_ptr<CAnimation>> idToProjectile; | ||||
| 	std::map<int, std::vector<CCreature::CreatureAnimation::RayColor>> idToRay; | ||||
|  | ||||
| 	std::map<std::string, std::shared_ptr<CAnimation>> animationsCache; | ||||
| 	std::map<si32, std::shared_ptr<CAnimation>> obstacleAnimations; | ||||
|  | ||||
| @@ -197,7 +184,6 @@ private: | ||||
| 	//force active stack to cast a spell if possible | ||||
| 	void enterCreatureCastingMode(); | ||||
|  | ||||
| 	std::list<ProjectileInfo> projectiles; //projectiles flying on battlefield | ||||
| 	void giveCommand(EActionType action, BattleHex tile = BattleHex(), si32 additional = -1); | ||||
| 	void sendCommand(BattleAction *& command, const CStack * actor = nullptr); | ||||
|  | ||||
| @@ -269,7 +255,6 @@ private: | ||||
| 	void showPiecesOfWall(SDL_Surface *to, std::vector<int> pieces); | ||||
|  | ||||
| 	void showBattleEffects(SDL_Surface *to, const std::vector<const BattleEffect *> &battleEffects); | ||||
| 	void showProjectiles(SDL_Surface *to); | ||||
|  | ||||
| 	BattleObjectsByHex sortObjectsByHex(); | ||||
| 	void updateBattleAnimations(); | ||||
| @@ -283,6 +268,8 @@ private: | ||||
|  | ||||
| 	void setHeroAnimation(ui8 side, int phase); | ||||
| public: | ||||
| 	std::unique_ptr<CBattleProjectileController> projectilesController; | ||||
|  | ||||
| 	static CondSh<bool> animsAreDisplayed; //for waiting with the end of battle for end of anims | ||||
| 	static CondSh<BattleAction *> givenCommand; //data != nullptr if we have i.e. moved current unit | ||||
|  | ||||
| @@ -388,7 +375,6 @@ public: | ||||
|  | ||||
| 	void gateStateChanged(const EGateState state); | ||||
|  | ||||
| 	void initStackProjectile(const CStack * stack); | ||||
|  | ||||
| 	const CGHeroInstance *currentHero() const; | ||||
| 	InfoAboutHero enemyHero() const; | ||||
| @@ -410,4 +396,5 @@ public: | ||||
| 	friend class CShootingAnimation; | ||||
| 	friend class CCastAnimation; | ||||
| 	friend class CClickableHex; | ||||
| 	friend class CBattleProjectileController; | ||||
| }; | ||||
|   | ||||
							
								
								
									
										12
									
								
								client/battle/CBattleObstacleController.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								client/battle/CBattleObstacleController.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| /* | ||||
|  * CBattleObstacleController.cpp, part of VCMI engine | ||||
|  * | ||||
|  * Authors: listed in file AUTHORS in main folder | ||||
|  * | ||||
|  * License: GNU General Public License v2.0 or later | ||||
|  * Full text of license available in license.txt file, in main folder | ||||
|  * | ||||
|  */ | ||||
| #include "StdInc.h" | ||||
| #include "CBattleObstacleController.h" | ||||
|  | ||||
							
								
								
									
										15
									
								
								client/battle/CBattleObstacleController.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								client/battle/CBattleObstacleController.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| /* | ||||
|  * CBattleObstacleController.h, part of VCMI engine | ||||
|  * | ||||
|  * Authors: listed in file AUTHORS in main folder | ||||
|  * | ||||
|  * License: GNU General Public License v2.0 or later | ||||
|  * Full text of license available in license.txt file, in main folder | ||||
|  * | ||||
|  */ | ||||
| #pragma once | ||||
|  | ||||
| class CBattleObstacleController | ||||
| { | ||||
|  | ||||
| }; | ||||
							
								
								
									
										320
									
								
								client/battle/CBattleProjectileController.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										320
									
								
								client/battle/CBattleProjectileController.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,320 @@ | ||||
| /* | ||||
|  * CBattleProjectileController.cpp, part of VCMI engine | ||||
|  * | ||||
|  * Authors: listed in file AUTHORS in main folder | ||||
|  * | ||||
|  * License: GNU General Public License v2.0 or later | ||||
|  * Full text of license available in license.txt file, in main folder | ||||
|  * | ||||
|  */ | ||||
| #include "StdInc.h" | ||||
| #include "CBattleProjectileController.h" | ||||
| #include "../gui/Geometries.h" | ||||
| #include "../../lib/CStack.h" | ||||
| #include "../../lib/mapObjects/CGTownInstance.h" | ||||
| #include "../CGameInfo.h" | ||||
| #include "../gui/CAnimation.h" | ||||
| #include "CBattleInterface.h" | ||||
| #include "CCreatureAnimation.h" | ||||
|  | ||||
| CatapultProjectileInfo::CatapultProjectileInfo(const Point &from, const Point &dest) | ||||
| { | ||||
| 	facA = 0.005; // seems to be constant | ||||
|  | ||||
| 	// system of 2 linear equations, solutions of which are missing coefficients | ||||
| 	// for quadratic equation a*x*x + b*x + c | ||||
| 	double eq[2][3] = { | ||||
| 		{ static_cast<double>(from.x), 1.0, from.y - facA*from.x*from.x }, | ||||
| 		{ static_cast<double>(dest.x), 1.0, dest.y - facA*dest.x*dest.x } | ||||
| 	}; | ||||
|  | ||||
| 	// solve system via determinants | ||||
| 	double det  = eq[0][0] *eq[1][1] - eq[1][0] *eq[0][1]; | ||||
| 	double detB = eq[0][2] *eq[1][1] - eq[1][2] *eq[0][1]; | ||||
| 	double detC = eq[0][0] *eq[1][2] - eq[1][0] *eq[0][2]; | ||||
|  | ||||
| 	facB = detB / det; | ||||
| 	facC = detC / det; | ||||
|  | ||||
| 	// make sure that parabola is correct e.g. passes through from and dest | ||||
| 	assert(fabs(calculateY(from.x) - from.y) < 1.0); | ||||
| 	assert(fabs(calculateY(dest.x) - dest.y) < 1.0); | ||||
| } | ||||
|  | ||||
| double CatapultProjectileInfo::calculateY(double x) | ||||
| { | ||||
| 	return facA *pow(x, 2.0) + facB *x + facC; | ||||
| } | ||||
|  | ||||
| CBattleProjectileController::CBattleProjectileController(CBattleInterface * owner): | ||||
| 	owner(owner) | ||||
| { | ||||
|  | ||||
| } | ||||
|  | ||||
| void CBattleProjectileController::initStackProjectile(const CStack * stack) | ||||
| { | ||||
| 	const CCreature * creature;//creature whose shots should be loaded | ||||
| 	if(stack->getCreature()->idNumber == CreatureID::ARROW_TOWERS) | ||||
| 		creature = CGI->creh->objects[owner->siegeH->town->town->clientInfo.siegeShooter]; | ||||
| 	else | ||||
| 		creature = stack->getCreature(); | ||||
|  | ||||
| 	if (creature->animation.projectileRay.empty()) | ||||
| 	{ | ||||
| 		std::shared_ptr<CAnimation> projectile = std::make_shared<CAnimation>(creature->animation.projectileImageName); | ||||
| 		projectile->preload(); | ||||
|  | ||||
| 		if(projectile->size(1) != 0) | ||||
| 			logAnim->error("Expected empty group 1 in stack projectile"); | ||||
| 		else | ||||
| 			projectile->createFlippedGroup(0, 1); | ||||
|  | ||||
| 		idToProjectile[stack->getCreature()->idNumber] = projectile; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		idToRay[stack->getCreature()->idNumber] = creature->animation.projectileRay; | ||||
| 	} | ||||
| } | ||||
|  | ||||
|  | ||||
| void CBattleProjectileController::showProjectiles(SDL_Surface *to) | ||||
| { | ||||
| 	assert(to); | ||||
|  | ||||
| 	std::list< std::list<ProjectileInfo>::iterator > toBeDeleted; | ||||
| 	for (auto it = projectiles.begin(); it!=projectiles.end(); ++it) | ||||
| 	{ | ||||
| 		// Check if projectile is already visible (shooter animation did the shot) | ||||
| 		if (!it->shotDone) | ||||
| 		{ | ||||
| 			// frame we're waiting for is reached OR animation has already finished | ||||
| 			if (owner->creAnims[it->stackID]->getCurrentFrame() >= it->animStartDelay || | ||||
| 				owner->creAnims[it->stackID]->isShooting() == false) | ||||
| 			{ | ||||
| 				//at this point projectile should become visible | ||||
| 				owner->creAnims[it->stackID]->pause(); // pause animation | ||||
| 				it->shotDone = true; | ||||
| 			} | ||||
| 			else | ||||
| 				continue; // wait... | ||||
| 		} | ||||
|  | ||||
| 		if (idToProjectile.count(it->creID)) | ||||
| 		{ | ||||
| 			size_t group = it->reverse ? 1 : 0; | ||||
| 			auto image = idToProjectile[it->creID]->getImage(it->frameNum, group, true); | ||||
|  | ||||
| 			if(image) | ||||
| 			{ | ||||
| 				SDL_Rect dst; | ||||
| 				dst.h = image->height(); | ||||
| 				dst.w = image->width(); | ||||
| 				dst.x = static_cast<int>(it->x - dst.w / 2); | ||||
| 				dst.y = static_cast<int>(it->y - dst.h / 2); | ||||
|  | ||||
| 				image->draw(to, &dst, nullptr); | ||||
| 			} | ||||
| 		} | ||||
| 		if (idToRay.count(it->creID)) | ||||
| 		{ | ||||
| 			auto const & ray = idToRay[it->creID]; | ||||
|  | ||||
| 			if (std::abs(it->dx) > std::abs(it->dy)) // draw in horizontal axis | ||||
| 			{ | ||||
| 				int y1 =  it->y0 - ray.size() / 2; | ||||
| 				int y2 =  it->y - ray.size() / 2; | ||||
|  | ||||
| 				int x1 = it->x0; | ||||
| 				int x2 = it->x; | ||||
|  | ||||
| 				for (size_t i = 0; i < ray.size(); ++i) | ||||
| 				{ | ||||
| 					SDL_Color beginColor{ ray[i].r1, ray[i].g1, ray[i].b1, ray[i].a1}; | ||||
| 					SDL_Color endColor  { ray[i].r2, ray[i].g2, ray[i].b2, ray[i].a2}; | ||||
|  | ||||
| 					CSDL_Ext::drawLine(to, x1, y1 + i, x2, y2 + i, beginColor, endColor); | ||||
| 				} | ||||
| 			} | ||||
| 			else // draw in vertical axis | ||||
| 			{ | ||||
| 				int x1 = it->x0 - ray.size() / 2; | ||||
| 				int x2 = it->x - ray.size() / 2; | ||||
|  | ||||
| 				int y1 =  it->y0; | ||||
| 				int y2 =  it->y; | ||||
|  | ||||
| 				for (size_t i = 0; i < ray.size(); ++i) | ||||
| 				{ | ||||
| 					SDL_Color beginColor{ ray[i].r1, ray[i].g1, ray[i].b1, ray[i].a1}; | ||||
| 					SDL_Color endColor  { ray[i].r2, ray[i].g2, ray[i].b2, ray[i].a2}; | ||||
|  | ||||
| 					CSDL_Ext::drawLine(to, x1 + i, y1, x2 + i, y2, beginColor, endColor); | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Update projectile | ||||
| 		++it->step; | ||||
| 		if (it->step > it->lastStep) | ||||
| 		{ | ||||
| 			toBeDeleted.insert(toBeDeleted.end(), it); | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			if (it->catapultInfo) | ||||
| 			{ | ||||
| 				// Parabolic shot of the trajectory, as follows: f(x) = ax^2 + bx + c | ||||
| 				it->x += it->dx; | ||||
| 				it->y = it->catapultInfo->calculateY(it->x); | ||||
|  | ||||
| 				++(it->frameNum); | ||||
| 				it->frameNum %= idToProjectile[it->creID]->size(0); | ||||
| 			} | ||||
| 			else | ||||
| 			{ | ||||
| 				// Normal projectile, just add the calculated "deltas" to the x and y positions. | ||||
| 				it->x += it->dx; | ||||
| 				it->y += it->dy; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for (auto & elem : toBeDeleted) | ||||
| 	{ | ||||
| 		// resume animation | ||||
| 		owner->creAnims[elem->stackID]->play(); | ||||
| 		projectiles.erase(elem); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| bool CBattleProjectileController::hasActiveProjectile(const CStack * stack) | ||||
| { | ||||
| 	for(auto const & instance : projectiles) | ||||
| 	{ | ||||
| 		if(instance.creID == stack->getCreature()->idNumber) | ||||
| 		{ | ||||
| 			return true; | ||||
| 		} | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
|  | ||||
| void CBattleProjectileController::createProjectile(const CStack * shooter, const CStack * target, Point from, Point dest) | ||||
| { | ||||
| 	// Get further info about the shooter e.g. relative pos of projectile to unit. | ||||
| 	// If the creature id is 149 then it's a arrow tower which has no additional info so get the | ||||
| 	// actual arrow tower shooter instead. | ||||
| 	const CCreature *shooterInfo = shooter->getCreature(); | ||||
|  | ||||
| 	if(shooterInfo->idNumber == CreatureID::ARROW_TOWERS) | ||||
| 	{ | ||||
| 		int creID = owner->siegeH->town->town->clientInfo.siegeShooter; | ||||
| 		shooterInfo = CGI->creh->operator[](creID); | ||||
| 	} | ||||
| 	if(!shooterInfo->animation.missleFrameAngles.size()) | ||||
| 		logAnim->error("Mod error: Creature '%s' on the Archer's tower is not a shooter. Mod should be fixed. Trying to use archer's data instead..." | ||||
| 			, shooterInfo->nameSing); | ||||
|  | ||||
| 	auto & angles = shooterInfo->animation.missleFrameAngles.size() | ||||
| 		? shooterInfo->animation.missleFrameAngles | ||||
| 		: CGI->creh->operator[](CreatureID::ARCHER)->animation.missleFrameAngles; | ||||
|  | ||||
| 	// recalculate angle taking in account offsets | ||||
| 	//projectileAngle = atan2(fabs(destPos.y - spi.y), fabs(destPos.x - spi.x)); | ||||
| 	//if(shooter->position < dest) | ||||
| 	//	projectileAngle = -projectileAngle; | ||||
|  | ||||
| 	ProjectileInfo spi; | ||||
| 	spi.shotDone = false; | ||||
| 	spi.creID = shooter->getCreature()->idNumber; | ||||
| 	spi.stackID = shooter->ID; | ||||
| 	// reverse if creature is facing right OR this is non-existing stack that is not tower (war machines) | ||||
| 	spi.reverse = shooter ? !owner->creDir[shooter->ID] : shooter->getCreature()->idNumber != CreatureID::ARROW_TOWERS; | ||||
|  | ||||
| 	spi.step = 0; | ||||
| 	spi.frameNum = 0; | ||||
|  | ||||
| 	spi.x0 = from.x; | ||||
| 	spi.y0 = from.y; | ||||
|  | ||||
| 	spi.x = from.x; | ||||
| 	spi.y = from.y; | ||||
|  | ||||
| 	if (target) | ||||
| 	{ | ||||
| 		double animSpeed = AnimationControls::getProjectileSpeed(); // flight speed of projectile | ||||
| 		double distanceSquared = (dest.x - spi.x) * (dest.x - spi.x) + (dest.y - spi.y) * (dest.y - spi.y); | ||||
| 		double distance = sqrt(distanceSquared); | ||||
| 		spi.lastStep = std::round(distance / animSpeed); | ||||
| 		if(spi.lastStep == 0) | ||||
| 			spi.lastStep = 1; | ||||
| 		spi.dx = (dest.x - spi.x) / spi.lastStep; | ||||
| 		spi.dy = (dest.y - spi.y) / spi.lastStep; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		// Catapult attack | ||||
| 		spi.catapultInfo.reset(new CatapultProjectileInfo(Point((int)spi.x, (int)spi.y), dest)); | ||||
|  | ||||
| 		double animSpeed = AnimationControls::getProjectileSpeed() / 10; | ||||
| 		spi.lastStep = static_cast<int>(std::abs((dest.x - spi.x) / animSpeed)); | ||||
| 		spi.dx = animSpeed; | ||||
| 		spi.dy = 0; | ||||
|  | ||||
| 		auto img = idToProjectile[spi.creID]->getImage(0); | ||||
|  | ||||
| 		// Add explosion anim | ||||
| 		Point animPos(dest.x - 126 + img->width() / 2, | ||||
| 					  dest.y - 105 + img->height() / 2); | ||||
|  | ||||
| 		//owner->addNewAnim( new CEffectAnimation(owner, catapultDamage ? "SGEXPL.DEF" : "CSGRCK.DEF", animPos.x, animPos.y)); | ||||
| 	} | ||||
| 	double pi = std::atan(1)*4; | ||||
|  | ||||
| 	//in some cases (known one: hero grants shooter bonus to unit) the shooter stack's projectile may not be properly initialized | ||||
| 	if (!idToProjectile.count(spi.creID) && !idToRay.count(spi.creID)) | ||||
| 		initStackProjectile(shooter); | ||||
|  | ||||
| 	if (idToProjectile.count(spi.creID)) | ||||
| 	{ | ||||
| 		// only frames below maxFrame are usable: anything  higher is either no present or we don't know when it should be used | ||||
| 		size_t maxFrame = std::min<size_t>(angles.size(), idToProjectile.at(spi.creID)->size(0)); | ||||
|  | ||||
| 		assert(maxFrame > 0); | ||||
| 		double projectileAngle = atan2(fabs((double)dest.y - from.y), fabs((double)dest.x - from.x)); | ||||
| 		//if(shooter->getPosition() < dest) | ||||
| 		//	projectileAngle = -projectileAngle; | ||||
|  | ||||
| 		// values in angles array indicate position from which this frame was rendered, in degrees. | ||||
| 		// find frame that has closest angle to one that we need for this shot | ||||
| 		size_t bestID = 0; | ||||
| 		double bestDiff = fabs( angles[0] / 180 * pi - projectileAngle ); | ||||
|  | ||||
| 		for (size_t i=1; i<maxFrame; i++) | ||||
| 		{ | ||||
| 			double currentDiff = fabs( angles[i] / 180 * pi - projectileAngle ); | ||||
| 			if (currentDiff < bestDiff) | ||||
| 			{ | ||||
| 				bestID = i; | ||||
| 				bestDiff = currentDiff; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		spi.frameNum = static_cast<int>(bestID); | ||||
| 	} | ||||
| 	else if (idToRay.count(spi.creID)) | ||||
| 	{ | ||||
| 		// no-op | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		logGlobal->error("Unable to find valid projectile for shooter %d", spi.creID); | ||||
| 	} | ||||
|  | ||||
| 	// Set projectile animation start delay which is specified in frames | ||||
| 	spi.animStartDelay = shooterInfo->animation.attackClimaxFrame; | ||||
| 	projectiles.push_back(spi); | ||||
| } | ||||
							
								
								
									
										65
									
								
								client/battle/CBattleProjectileController.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								client/battle/CBattleProjectileController.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| /* | ||||
|  * CBattleSiegeController.h, part of VCMI engine | ||||
|  * | ||||
|  * Authors: listed in file AUTHORS in main folder | ||||
|  * | ||||
|  * License: GNU General Public License v2.0 or later | ||||
|  * Full text of license available in license.txt file, in main folder | ||||
|  * | ||||
|  */ | ||||
| #pragma once | ||||
|  | ||||
| #include "../../lib/CCreatureHandler.h" | ||||
|  | ||||
| struct Point; | ||||
| struct SDL_Surface; | ||||
| class CAnimation; | ||||
| class CStack; | ||||
| class CBattleInterface; | ||||
|  | ||||
| /// Small struct which is needed for drawing the parabolic trajectory of the catapult cannon | ||||
| struct CatapultProjectileInfo | ||||
| { | ||||
| 	CatapultProjectileInfo(const Point &from, const Point &dest); | ||||
|  | ||||
| 	double facA, facB, facC; | ||||
|  | ||||
| 	double calculateY(double x); | ||||
| }; | ||||
|  | ||||
| /// Small struct which contains information about the position and the velocity of a projectile | ||||
| struct ProjectileInfo | ||||
| { | ||||
| 	double x0, y0; //initial position on the screen | ||||
| 	double x, y; //position on the screen | ||||
| 	double dx, dy; //change in position in one step | ||||
| 	int step, lastStep; //to know when finish showing this projectile | ||||
| 	int creID; //ID of creature that shot this projectile | ||||
| 	int stackID; //ID of stack | ||||
| 	int frameNum; //frame to display form projectile animation | ||||
| 	//bool spin; //if true, frameNum will be increased | ||||
| 	int animStartDelay; //frame of shooter animation when projectile should appear | ||||
| 	bool shotDone; // actual shot already done, projectile is flying | ||||
| 	bool reverse; //if true, projectile will be flipped by vertical asix | ||||
| 	std::shared_ptr<CatapultProjectileInfo> catapultInfo; // holds info about the parabolic trajectory of the cannon | ||||
| }; | ||||
|  | ||||
| class CBattleProjectileController | ||||
| { | ||||
| 	CBattleInterface * owner; | ||||
|  | ||||
| 	std::map<int, std::shared_ptr<CAnimation>> idToProjectile; | ||||
| 	std::map<int, std::vector<CCreature::CreatureAnimation::RayColor>> idToRay; | ||||
|  | ||||
| 	std::list<ProjectileInfo> projectiles; //projectiles flying on battlefield | ||||
|  | ||||
| public: | ||||
| 	CBattleProjectileController(CBattleInterface * owner); | ||||
|  | ||||
| 	void showProjectiles(SDL_Surface *to); | ||||
| 	void initStackProjectile(const CStack * stack); | ||||
|  | ||||
| 	bool hasActiveProjectile(const CStack * stack); | ||||
|  | ||||
| 	void createProjectile(const CStack * shooter, const CStack * target, Point from, Point dest); | ||||
| }; | ||||
							
								
								
									
										12
									
								
								client/battle/CBattleSiegeController.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								client/battle/CBattleSiegeController.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| /* | ||||
|  * CBattleSiegeController.cpp, part of VCMI engine | ||||
|  * | ||||
|  * Authors: listed in file AUTHORS in main folder | ||||
|  * | ||||
|  * License: GNU General Public License v2.0 or later | ||||
|  * Full text of license available in license.txt file, in main folder | ||||
|  * | ||||
|  */ | ||||
| #include "StdInc.h" | ||||
| #include "CBattleSiegeController.h" | ||||
|  | ||||
							
								
								
									
										17
									
								
								client/battle/CBattleSiegeController.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								client/battle/CBattleSiegeController.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| /* | ||||
|  * CBattleObstacleController.h, part of VCMI engine | ||||
|  * | ||||
|  * Authors: listed in file AUTHORS in main folder | ||||
|  * | ||||
|  * License: GNU General Public License v2.0 or later | ||||
|  * Full text of license available in license.txt file, in main folder | ||||
|  * | ||||
|  */ | ||||
| #pragma once | ||||
|  | ||||
|  | ||||
|  | ||||
| class CBattleSiegeController | ||||
| { | ||||
|  | ||||
| }; | ||||
| @@ -701,6 +701,10 @@ namespace VCMIDirs | ||||
| 			#ifdef VCMI_WINDOWS | ||||
| 			std::locale::global(boost::locale::generator().generate("en_US.UTF-8")); | ||||
| 			#endif | ||||
|  | ||||
| 			#ifdef VCMI_XDG | ||||
| 			setenv("LC_ALL", "C", 1); | ||||
| 			#endif | ||||
| 			boost::filesystem::path::imbue(std::locale()); | ||||
|  | ||||
| 			singleton.init(); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user