Browse Source

Merge pull request #269 from dydzio0614/MakeWOGGreatAgain

Okay it's time to merge this awesome branch 👍
DjWarmonger 9 years ago
parent
commit
c91bc25e70

+ 1 - 1
client/battle/CBattleAnimations.cpp

@@ -799,7 +799,7 @@ bool CShootingAnimation::init()
 	double pi = boost::math::constants::pi<double>();
 
 	// 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[spi.creID]->ourImages.size());
+	size_t maxFrame = std::min<size_t>(angles.size(), owner->idToProjectile.at(spi.creID)->ourImages.size());
 
 	assert(maxFrame > 0);
 

+ 18 - 23
client/battle/CBattleInterface.cpp

@@ -302,29 +302,6 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
 		if (s->position >= 0) //turrets have position < 0
 			bfield[s->position]->accessible = false;
 
-	//loading projectiles for units
-	for (const CStack *s : stacks)
-	{
-		if (s->getCreature()->isShooting())
-		{
-			CDefHandler *&projectile = idToProjectile[s->getCreature()->idNumber];
-
-			const CCreature *creature;//creature whose shots should be loaded
-			if (s->getCreature()->idNumber == CreatureID::ARROW_TOWERS)
-				creature = CGI->creh->creatures[siegeH->town->town->clientInfo.siegeShooter];
-			else
-				creature = s->getCreature();
-
-			projectile = CDefHandler::giveDef(creature->animation.projectileImageName);
-
-			for (auto & elem : projectile->ourImages) //alpha transforming
-			{
-				CSDL_Ext::alphaTransform(elem.bitmap);
-			}
-		}
-	}
-
-
 	//preparing graphic with cell borders
 	cellBorders = CSDL_Ext::newSurface(background->w, background->h, cellBorder);
 	//copying palette
@@ -1005,6 +982,24 @@ void CBattleInterface::newStack(const CStack *stack)
 	creAnims[stack->ID]->pos.w = creAnims[stack->ID]->getWidth();
 	creAnims[stack->ID]->setType(CCreatureAnim::HOLDING);
 
+	//loading projectiles for units
+	if (stack->getCreature()->isShooting())
+	{
+		CDefHandler *&projectile = idToProjectile[stack->getCreature()->idNumber];
+
+		const CCreature *creature;//creature whose shots should be loaded
+		if (stack->getCreature()->idNumber == CreatureID::ARROW_TOWERS)
+			creature = CGI->creh->creatures[siegeH->town->town->clientInfo.siegeShooter];
+		else
+			creature = stack->getCreature();
+
+		projectile = CDefHandler::giveDef(creature->animation.projectileImageName);
+
+		for (auto & elem : projectile->ourImages) //alpha transforming
+		{
+			CSDL_Ext::alphaTransform(elem.bitmap);
+		}
+	}
 }
 
 void CBattleInterface::stackRemoved(int stackID)

+ 17 - 0
config/bonuses.json

@@ -442,6 +442,15 @@
 			"icon":  "zvs/Lib1.res/E_SHOOT"
 		}
 	},
+	
+	"SOUL_STEAL":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_SUMMON2"
+		}
+	},
+	
 	"SPELLCASTER":
 	{
 		"graphics":
@@ -515,6 +524,14 @@
 			"icon":  "zvs/Lib1.res/ThreeHeaded"
 		}
 	},
+	
+	"TRANSMUTATION":
+	{
+		"graphics":
+		{
+			"icon":  "zvs/Lib1.res/E_SGTYPE"
+		}
+	},
 
 	"UNDEAD":
 	{

+ 18 - 0
config/bonuses_texts.json

@@ -334,6 +334,12 @@
 		"name": "Ranged",
 		"description": "Creature can shoot"
 	},
+	
+	"SOUL_STEAL":
+	{
+		"name": "Soul Steal",
+		"description": "Gains ${val} new creatures for each enemy killed"
+	},
 
 	"SPELLCASTER":
 	{
@@ -376,6 +382,12 @@
 		"name": "Aura of Resistance",
 		"description": "Nearby stacks get ${val}% resistance"
 	},
+	
+	"SUMMON_GUARDIANS":
+	{
+		"name": "Summon guardians",
+		"description": "At battle start summons ${subtype.creature} (${val}%)"
+	},
 
 	"TWO_HEX_ATTACK_BREATH":
 	{
@@ -388,6 +400,12 @@
 		"name": "Three-headed attack",
 		"description": "Attacks three adjacent units"
 	},
+	
+	"TRANSMUTATION":
+	{
+		"name": "Transmutation",
+		"description": "${val}% chance to transform attacked unit to other type"
+	},
 
 	"UNDEAD":
 	{

+ 3 - 0
lib/HeroBonus.h

@@ -216,6 +216,9 @@ public:
 	BONUS_NAME(DISGUISED) /* subtype - spell level */\
 	BONUS_NAME(VISIONS) /* subtype - spell level */\
 	BONUS_NAME(NO_TERRAIN_PENALTY) /* subtype - terrain type */\
+	BONUS_NAME(SOUL_STEAL) /*val - number of units gained per enemy killed, subtype = 0 - gained units survive after battle, 1 - they do not*/ \
+	BONUS_NAME(TRANSMUTATION) /*val - chance to trigger in %, subtype = 0 - resurrection based on HP, 1 - based on unit count, additional info - target creature ID (attacker default)*/\
+	BONUS_NAME(SUMMON_GUARDIANS) /*val - amount in % of stack count, subtype = creature ID*/\
 	BONUS_NAME(CATAPULT_EXTRA_SHOTS) /*val - number of additional shots, requires CATAPULT bonus to work*/\
 	/* end of list */
 

+ 5 - 4
lib/NetPacks.h

@@ -1287,7 +1287,7 @@ struct BattleStackMoved : public CPackForClient
 struct StacksHealedOrResurrected : public CPackForClient
 {
 	StacksHealedOrResurrected()
-		:lifeDrain(false), tentHealing(false), drainedFrom(0), cure(false)
+		:lifeDrain(false), tentHealing(false), drainedFrom(0), cure(false), canOverheal(false)
 	{}
 
 	DLL_LINKAGE void applyGs(CGameState *gs);
@@ -1305,15 +1305,16 @@ struct StacksHealedOrResurrected : public CPackForClient
 	};
 
 	std::vector<HealInfo> healedStacks;
-	bool lifeDrain; //if true, this heal is an effect of life drain
+	bool lifeDrain; //if true, this heal is an effect of life drain or soul steal
 	bool tentHealing; //if true, than it's healing via First Aid Tent
-	si32 drainedFrom; //if life drain - then stack life was drain from, if tentHealing - stack that is a healer
+	si32 drainedFrom; //if life drain or soul steal - then stack life was drain from, if tentHealing - stack that is a healer
 	bool cure; //archangel cast also remove negative effects
+	bool canOverheal; //to allow healing over initial stack amount
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{
 		h & healedStacks & lifeDrain & tentHealing & drainedFrom;
-		h & cure;
+		h & cure & canOverheal;
 	}
 };
 

+ 5 - 2
lib/NetPacksLib.cpp

@@ -1616,8 +1616,11 @@ DLL_LINKAGE void StacksHealedOrResurrected::applyGs(CGameState *gs)
 
 			changedStack->state.insert(EBattleStackState::ALIVE);
 		}
-
-		int res = std::min(elem.healedHP / changedStack->MaxHealth() , changedStack->baseAmount - changedStack->count);
+		int res;
+		if(canOverheal) //for example WoG ghost soul steal ability allows getting more units than before battle
+			res = elem.healedHP / changedStack->MaxHealth();
+		else
+			res = std::min(elem.healedHP / changedStack->MaxHealth() , changedStack->baseAmount - changedStack->count);		
 		changedStack->count += res;
 		if(elem.lowLevelResurrection)
 			changedStack->resurrected += res;

+ 182 - 2
server/CGameHandler.cpp

@@ -137,6 +137,79 @@ static void giveExp(BattleResult &r)
 	}
 }
 
+static void SummonGuardiansHelper(std::vector<BattleHex> & output, const BattleHex & targetPosition, bool targetIsAttacker, bool targetIsTwoHex) //return hexes for summoning two hex monsters in output, target = unit to guard
+{
+	int x = targetPosition.getX();
+	int y = targetPosition.getY();
+
+	if (targetIsAttacker) //handle front guardians, TODO: should we handle situation when units start battle near opposite side of the battlefield? Cannot happen in normal H3...
+		BattleHex::checkAndPush(targetPosition.movedInDir(BattleHex::EDir::RIGHT, false).movedInDir(BattleHex::EDir::RIGHT, false), output);
+	else
+		BattleHex::checkAndPush(targetPosition.movedInDir(BattleHex::EDir::LEFT, false).movedInDir(BattleHex::EDir::LEFT, false), output);
+
+	//guardian spawn locations for four default position cases for attacker and defender, non-default starting location for att and def is handled in first two if's
+	if (targetIsAttacker && ((y % 2 == 0) || (x > 1)))
+	{
+		if (targetIsTwoHex && (y % 2 == 1) && (x == 2)) //handle exceptional case
+		{
+			BattleHex::checkAndPush(targetPosition.movedInDir(BattleHex::EDir::TOP_RIGHT, false), output);
+			BattleHex::checkAndPush(targetPosition.movedInDir(BattleHex::EDir::BOTTOM_RIGHT, false), output);
+		}
+		else
+		{	//add back-side guardians for two-hex target, side guardians for one-hex
+			BattleHex::checkAndPush(targetPosition.movedInDir(targetIsTwoHex ? BattleHex::EDir::TOP_LEFT : BattleHex::EDir::TOP_RIGHT, false), output);
+			BattleHex::checkAndPush(targetPosition.movedInDir(targetIsTwoHex ? BattleHex::EDir::BOTTOM_LEFT : BattleHex::EDir::BOTTOM_RIGHT, false), output);
+
+			if (!targetIsTwoHex && x > 2) //back guard for one-hex
+				BattleHex::checkAndPush(targetPosition.movedInDir(BattleHex::EDir::LEFT, false), output);
+			else if (targetIsTwoHex)//front-side guardians for two-hex target
+			{
+				BattleHex::checkAndPush(targetPosition.movedInDir(BattleHex::EDir::RIGHT, false).movedInDir(BattleHex::EDir::TOP_RIGHT, false), output);
+				BattleHex::checkAndPush(targetPosition.movedInDir(BattleHex::EDir::RIGHT, false).movedInDir(BattleHex::EDir::BOTTOM_RIGHT, false), output);
+				if (x > 3) //back guard for two-hex
+					BattleHex::checkAndPush(targetPosition.movedInDir(BattleHex::EDir::LEFT, false).movedInDir(BattleHex::EDir::LEFT, false), output);
+			}
+		}
+
+	}
+
+	else if (!targetIsAttacker && ((y % 2 == 1) || (x < GameConstants::BFIELD_WIDTH - 2)))
+	{
+		if (targetIsTwoHex && (y % 2 == 0) && (x == GameConstants::BFIELD_WIDTH - 3)) //handle exceptional case... equivalent for above for defender side
+		{
+			BattleHex::checkAndPush(targetPosition.movedInDir(BattleHex::EDir::TOP_LEFT, false), output);
+			BattleHex::checkAndPush(targetPosition.movedInDir(BattleHex::EDir::BOTTOM_LEFT, false), output);
+		}
+		else
+		{
+			BattleHex::checkAndPush(targetPosition.movedInDir(targetIsTwoHex ? BattleHex::EDir::TOP_RIGHT : BattleHex::EDir::TOP_LEFT, false), output);
+			BattleHex::checkAndPush(targetPosition.movedInDir(targetIsTwoHex ? BattleHex::EDir::BOTTOM_RIGHT : BattleHex::EDir::BOTTOM_LEFT, false), output);
+
+			if (!targetIsTwoHex && x < GameConstants::BFIELD_WIDTH - 3)
+				BattleHex::checkAndPush(targetPosition.movedInDir(BattleHex::EDir::RIGHT, false), output);
+			else if (targetIsTwoHex)
+			{
+				BattleHex::checkAndPush(targetPosition.movedInDir(BattleHex::EDir::LEFT, false).movedInDir(BattleHex::EDir::TOP_LEFT, false), output);
+				BattleHex::checkAndPush(targetPosition.movedInDir(BattleHex::EDir::LEFT, false).movedInDir(BattleHex::EDir::BOTTOM_LEFT, false), output);
+				if (x < GameConstants::BFIELD_WIDTH - 4)
+					BattleHex::checkAndPush(targetPosition.movedInDir(BattleHex::EDir::RIGHT, false).movedInDir(BattleHex::EDir::RIGHT, false), output);
+			}
+		}
+	}
+
+	else if (!targetIsAttacker && y % 2 == 0)
+	{
+		BattleHex::checkAndPush(targetPosition.movedInDir(BattleHex::EDir::LEFT, false).movedInDir(BattleHex::EDir::TOP_LEFT, false), output);
+		BattleHex::checkAndPush(targetPosition.movedInDir(BattleHex::EDir::LEFT, false).movedInDir(BattleHex::EDir::BOTTOM_LEFT, false), output);
+	}
+
+	else if (targetIsAttacker && y % 2 == 1)
+	{
+		BattleHex::checkAndPush(targetPosition.movedInDir(BattleHex::EDir::RIGHT, false).movedInDir(BattleHex::EDir::TOP_RIGHT, false), output);
+		BattleHex::checkAndPush(targetPosition.movedInDir(BattleHex::EDir::RIGHT, false).movedInDir(BattleHex::EDir::BOTTOM_RIGHT, false), output);
+	}
+}
+
 PlayerStatus PlayerStatuses::operator[](PlayerColor player)
 {
 	boost::unique_lock<boost::mutex> l(mx);
@@ -906,7 +979,34 @@ void CGameHandler::applyBattleEffects(BattleAttack &bat, const CStack *att, cons
 			bsa.healedStacks.push_back(shi);
 		}
 	}
-	bat.bsa.push_back(bsa); //add this stack to the list of victims after drain life has been calculated
+
+	//soul steal handling
+	if (att->hasBonusOfType(Bonus::SOUL_STEAL) && def->isLiving())
+	{
+		StacksHealedOrResurrected shi;
+		shi.lifeDrain = true;
+		shi.tentHealing = false;
+		shi.cure = false;
+		shi.canOverheal = true;
+		shi.drainedFrom = def->ID;
+
+		for (int i = 0; i < 2; i++) //we can have two bonuses - one with subtype 0 and another with subtype 1
+		{
+			if (att->hasBonusOfType(Bonus::SOUL_STEAL, i))
+			{
+				StacksHealedOrResurrected::HealInfo hi;
+				hi.stackID = att->ID;
+				hi.healedHP = bsa.killedAmount * att->valOfBonuses(Bonus::SOUL_STEAL, i) * att->MaxHealth();
+				hi.lowLevelResurrection = (bool)i;
+				shi.healedStacks.push_back(hi);
+			}
+		}
+		if (std::any_of(shi.healedStacks.begin(), shi.healedStacks.end(), [](StacksHealedOrResurrected::HealInfo healInfo) { return healInfo.healedHP > 0; }))
+		{
+			bsa.healedStacks.push_back(shi);
+		}
+	}
+	bat.bsa.push_back(bsa); //add this stack to the list of victims after drain life has been calculated 
 
 	//fire shield handling
 	if (!bat.shot() && !vstd::contains(def->state, EBattleStackState::CLONED) &&
@@ -5175,6 +5275,8 @@ void CGameHandler::handleAfterAttackCasting(const BattleAttack & bat)
 	if (!attacker || bat.bsa.empty()) // can be already dead
 		return;
 
+	const CStack *defender = gs->curB->battleGetStackByID(bat.bsa.at(0).stackAttacked);
+
 	auto cast = [=](SpellID spellID, int power)
 	{
 		const CSpell * spell = SpellID(spellID).toSpell();
@@ -5233,6 +5335,44 @@ void CGameHandler::handleAfterAttackCasting(const BattleAttack & bat)
 	{
 		cast(SpellID::ACID_BREATH_DAMAGE, acidDamage * attacker->count);
 	}
+
+	if (attacker->hasBonusOfType(Bonus::TRANSMUTATION) && defender->isLiving()) //transmutation mechanics, similar to WoG werewolf ability
+	{
+		double chanceToTrigger = attacker->valOfBonuses(Bonus::TRANSMUTATION) / 100.0f;
+		vstd::amin(chanceToTrigger, 1); //cap at 100%
+
+		if (getRandomGenerator().getDoubleRange(0, 1)() > chanceToTrigger)
+			return;
+		
+		int bonusAdditionalInfo = attacker->getBonus(Selector::type(Bonus::TRANSMUTATION))->additionalInfo;
+
+		if (defender->getCreature()->idNumber == bonusAdditionalInfo ||
+			(bonusAdditionalInfo == -1 && defender->getCreature()->idNumber == attacker->getCreature()->idNumber))
+			return;
+
+		BattleStackAdded resurrectInfo;
+		resurrectInfo.pos = defender->position;
+
+		if (bonusAdditionalInfo != -1)
+			resurrectInfo.creID = (CreatureID)bonusAdditionalInfo;
+		else
+			resurrectInfo.creID = attacker->getCreature()->idNumber;
+		
+		if (attacker->hasBonusOfType((Bonus::TRANSMUTATION), 0))
+		{
+			resurrectInfo.amount = std::max((defender->count * defender->MaxHealth()) / resurrectInfo.creID.toCreature()->MaxHealth(), 1u);
+		}
+		else if (attacker->hasBonusOfType((Bonus::TRANSMUTATION), 1))
+			resurrectInfo.amount = defender->count;
+		else
+			return; //wrong subtype
+
+		BattleStacksRemoved victimInfo;
+		victimInfo.stackIDs.insert(bat.bsa.at(0).stackAttacked);
+
+		sendAndApply(&victimInfo);
+		sendAndApply(&resurrectInfo);
+	}
 }
 
 bool CGameHandler::castSpell(const CGHeroInstance *h, SpellID spellID, const int3 &pos)
@@ -5484,8 +5624,42 @@ void CGameHandler::runBattle()
 	}
 
 	//initial stacks appearance triggers, e.g. built-in bonus spells
-	for (auto stack : gs->curB->stacks)
+	auto initialStacks = gs->curB->stacks; //use temporary variable to outclude summoned stacks added to gs->curB->stacks from processing
+
+	for (CStack * stack : initialStacks)
 	{
+		if (stack->hasBonusOfType(Bonus::SUMMON_GUARDIANS))
+		{
+			const std::shared_ptr<Bonus> summonInfo = stack->getBonus(Selector::type(Bonus::SUMMON_GUARDIANS));
+			auto accessibility = getAccesibility();
+			CreatureID creatureData = CreatureID(summonInfo->subtype);
+			std::vector<BattleHex> targetHexes;
+			bool targetIsBig = stack->getCreature()->isDoubleWide(); //target = creature to guard
+
+			/*Chosen idea for two hex units was to cover all possible surrounding hexes of target unit with as small number of stacks as possible.
+			For one-hex targets there are four guardians - front, back and one per side (up + down).
+			Two-hex targets are wider and the difference is there are two guardians per side to cover 3 hexes + extra hex in the front
+			Additionally, there are special cases for starting positions etc., where guardians would be outside of battlefield if spawned normally*/
+			if (!creatureData.toCreature()->isDoubleWide())
+				targetHexes = stack->getSurroundingHexes();
+			else
+				SummonGuardiansHelper(targetHexes, stack->position, stack->attackerOwned, stack->getCreature()->isDoubleWide());
+				
+			for (auto hex : targetHexes)
+			{
+				if (accessibility.accessible(hex, creatureData.toCreature()->isDoubleWide(), stack->attackerOwned)) //without this multiple creatures can occupy one hex
+				{
+					BattleStackAdded newStack;
+					newStack.amount = std::max(1, (int)(stack->count * 0.01 * summonInfo->val));
+					newStack.creID = creatureData.num;
+					newStack.attacker = stack->attackerOwned;
+					newStack.summoned = true;
+					newStack.pos = hex.hex;
+					sendAndApply(&newStack);
+				}
+			}
+		}
+
 		stackAppearTrigger(stack);
 	}
 
@@ -6230,6 +6404,12 @@ CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance * _army, Battl
 				StackLocation sl(army, st->slot);
 				newStackCounts.push_back(TStackAndItsNewCount(sl, st->count));
 			}
+			else if (st->count > army->getStackCount(st->slot))
+			{
+				logGlobal->debug("Stack gained %d units.", st->count - army->getStackCount(st->slot));
+				StackLocation sl(army, st->slot);
+				newStackCounts.push_back(TStackAndItsNewCount(sl, st->count));
+			}
 		}
 		else
 		{