mirror of
https://github.com/vcmi/vcmi.git
synced 2025-01-02 00:10:22 +02:00
Merge pull request #269 from dydzio0614/MakeWOGGreatAgain
Okay it's time to merge this awesome branch 👍
This commit is contained in:
commit
c91bc25e70
@ -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);
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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":
|
||||
{
|
||||
|
@ -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":
|
||||
{
|
||||
|
@ -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 */
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user