diff --git a/config/bonusnames.json b/config/bonusnames.json index 011a2320c..38af269c3 100644 --- a/config/bonusnames.json +++ b/config/bonusnames.json @@ -15,7 +15,7 @@ { "id": "DEFENSIVE_STANCE", "name": "Defense Bonus", "description": "+%d Defense when defending" }, { "id": "ADDITIONAL_ATTACK", "name": "Double Strike", "description": "Attacks twice" }, { "id": "DRAGON_NATURE", "name": "Dragon", "description": "Creature has a Dragon Nature" }, - { "id": "LIFE_DRAIN", "name": "Drain life", "description": "Drains life equal to damage dealt" }, + { "id": "LIFE_DRAIN", "name": "Drain life (%d%)", "description": "Drains life equal to damage dealt" }, { "id": "FEAR", "name": "Fear", "description": "Causes Fear on an enemy stack" }, { "id": "FEARLESS", "name": "Fearless", "description": "Immune to Fear ability" }, { "id": "FLYING", "name": "Fly", "description": "Can Fly (ignores obstacles)" }, diff --git a/config/creatures/castle.json b/config/creatures/castle.json index 3b5179b65..3817b8121 100644 --- a/config/creatures/castle.json +++ b/config/creatures/castle.json @@ -152,6 +152,7 @@ "id": 7, "level": 4, "faction": "castle", + "abilities": [ [ "ADDITIONAL_ATTACK", 1, 0, 0 ] ], "graphics" : { "animation": "CCRUSD.DEF" diff --git a/config/creatures/necropolis.json b/config/creatures/necropolis.json index 81692f071..89b5a03fb 100644 --- a/config/creatures/necropolis.json +++ b/config/creatures/necropolis.json @@ -142,7 +142,7 @@ "id": 63, "level": 4, "faction": "necropolis", - "abilities": [ [ "LIFE_DRAIN", 0, 0, 0 ], + "abilities": [ [ "LIFE_DRAIN", 100, 0, 0 ], //drain 100% of damage dealt [ "BLOCKS_RETALIATION", 0, 0, 0 ] ], //vampire lords "graphics" : { diff --git a/config/creatures/rampart.json b/config/creatures/rampart.json index a00d40b3b..7decbd222 100644 --- a/config/creatures/rampart.json +++ b/config/creatures/rampart.json @@ -107,6 +107,13 @@ "id": 19, "level": 3, "faction": "rampart", + "abilities": [ + { + "type": "ADDITIONAL_ATTACK", + "val" : 1, + "effectRange": "ONLY_DISTANCE_FIGHT" + } + ], "graphics" : { "animation": "CGRELF.DEF", diff --git a/config/creatures/wog.json b/config/creatures/wog.json index 241b57ad5..23735be1a 100644 --- a/config/creatures/wog.json +++ b/config/creatures/wog.json @@ -50,7 +50,8 @@ "id": 154, "level": 8, "faction": "necropolis", - "abilities": [ [ "DRAGON_NATURE", 0, 0, 0 ] ], //blood dragon is a dragon + "abilities": [ [ "LIFE_DRAIN", 40, 0, 0 ], //40% + [ "DRAGON_NATURE", 0, 0, 0 ] ], //blood dragon is a dragon "graphics" : { "animation": "ZM154Z.DEF" diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index 15616ab45..c73c032f8 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -263,8 +263,6 @@ void CCreatureHandler::loadCreatures() ncre.addBonus(0, Bonus::SHOOTER); if(boost::algorithm::find_first(ncre.abilityRefs, "SIEGE_WEAPON")) ncre.addBonus(0, Bonus::SIEGE_WEAPON); - if(boost::algorithm::find_first(ncre.abilityRefs, "const_two_attacks")) - ncre.addBonus(1, Bonus::ADDITIONAL_ATTACK); if(boost::algorithm::find_first(ncre.abilityRefs, "const_free_attack")) ncre.addBonus(0, Bonus::BLOCKS_RETALIATION); if(boost::algorithm::find_first(ncre.abilityRefs, "IS_UNDEAD")) @@ -324,7 +322,10 @@ void CCreatureHandler::loadCreatures() } BOOST_FOREACH(const JsonNode &ability, node.second["abilities"].Vector()) { - AddAbility(c, ability.Vector()); + if (ability.getType() == JsonNode::DATA_VECTOR) + AddAbility(c, ability.Vector()); + else + c->addNewBonus(JsonUtils::parseBonus(ability)); } loadCreatureJson(c, node.second); diff --git a/lib/CCreatureSet.cpp b/lib/CCreatureSet.cpp index 18d86ca69..ea33f0739 100644 --- a/lib/CCreatureSet.cpp +++ b/lib/CCreatureSet.cpp @@ -593,8 +593,9 @@ std::string CStackInstance::bonusToString(Bonus *bonus, bool description) const case Bonus::ATTACKS_ALL_ADJACENT: case Bonus::ADDITIONAL_ATTACK: //TODO: what with more than one attack? Axe of Ferocity for example case Bonus::FULL_HP_REGENERATION: + case Bonus::MANA_DRAIN: + case Bonus::LIFE_DRAIN: case Bonus::REBIRTH: - case Bonus::LIFE_DRAIN: //TODO: chance, hp percentage? case Bonus::SELF_MORALE: case Bonus::SELF_LUCK: case Bonus::FEAR: @@ -616,7 +617,6 @@ std::string CStackInstance::bonusToString(Bonus *bonus, bool description) const case Bonus::SPELL_RESISTANCE_AURA: case Bonus::SPELL_DAMAGE_REDUCTION: case Bonus::LEVEL_SPELL_IMMUNITY: - case Bonus::MANA_DRAIN: case Bonus::HP_REGENERATION: case Bonus::ADDITIONAL_RETALIATION: case Bonus::DEFENSIVE_STANCE: @@ -667,6 +667,7 @@ std::string CStackInstance::bonusToString(Bonus *bonus, bool description) const case Bonus::ENEMY_DEFENCE_REDUCTION: case Bonus::REBIRTH: case Bonus::DEATH_STARE: + case Bonus::LIFE_DRAIN: boost::algorithm::replace_first(text, "%d", boost::lexical_cast(valOfBonuses(Selector::typeSubtype(bonus->type, bonus->subtype)))); break; case Bonus::HATE: @@ -905,6 +906,8 @@ std::string CStackInstance::bonusToGraphics(Bonus *bonus) const fileName = "ManaChannel.bmp"; break; case Bonus::MANA_DRAIN: fileName = "ManaDrain.bmp"; break; + case Bonus::LIFE_DRAIN: + fileName = "DrainLife.bmp"; break; } if(!fileName.empty()) fileName = "zvs/Lib1.res/" + fileName; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 3b2b4b3c4..5613cd2df 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -863,7 +863,8 @@ void CGameHandler::applyBattleEffects(BattleAttack &bat, const CStack *att, cons StacksHealedOrResurrected::HealInfo hi; hi.stackID = att->ID; - hi.healedHP = std::min(bsa.damageAmount, att->MaxHealth() - att->firstHPleft + att->MaxHealth() * (att->baseAmount - att->count) ); + hi.healedHP = std::min (bsa.damageAmount * att->valOfBonuses (Bonus::LIFE_DRAIN) / 100, + att->MaxHealth() - att->firstHPleft + att->MaxHealth() * (att->baseAmount - att->count) ); hi.lowLevelResurrection = false; shi.healedStacks.push_back(hi); @@ -3457,38 +3458,32 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) } //attack - if(stack->alive()) //move can cause death, eg. by walking into the moat - { - BattleAttack bat; - prepareAttack(bat, stack, stackAtEnd, distance, ba.additionalInfo); - handleAttackBeforeCasting(bat); //only before first attack - sendAndApply(&bat); - handleAfterAttackCasting(bat); - } - //counterattack - if(!stack->hasBonusOfType(Bonus::BLOCKS_RETALIATION) - && stackAtEnd->ableToRetaliate() - && stack->alive()) //attacker may have died (fire shield) - { - BattleAttack bat; - prepareAttack(bat, stackAtEnd, stack, 0, stack->position); - bat.flags |= BattleAttack::COUNTER; - sendAndApply(&bat); - handleAfterAttackCasting(bat); - } + int totalAttacks = 1 + stack->getBonuses(Selector::type (Bonus::ADDITIONAL_ATTACK), + (Selector::effectRange (Bonus::NO_LIMIT) || Selector::effectRange(Bonus::ONLY_MELEE_FIGHT)))->totalValue(); //all unspicified attacks + melee attacks - //second attack - if(stack //FIXME: clones tend to disapear during actions - && stack->valOfBonuses(Bonus::ADDITIONAL_ATTACK) > 0 - && !stack->hasBonusOfType(Bonus::SHOOTER) - && stack->alive() - && stackAtEnd->alive() ) + for (int i = 0; i < totalAttacks; ++i) { - BattleAttack bat; - prepareAttack(bat, stack, stackAtEnd, 0, ba.additionalInfo); - sendAndApply(&bat); - handleAfterAttackCasting(bat); + if( stack &&stack->alive()) //move can cause death, eg. by walking into the moat + { + BattleAttack bat; + prepareAttack(bat, stack, stackAtEnd, distance, ba.additionalInfo); + handleAttackBeforeCasting(bat); //only before first attack + sendAndApply(&bat); + handleAfterAttackCasting(bat); + } + + //counterattack + if(!stack->hasBonusOfType(Bonus::BLOCKS_RETALIATION) + && stackAtEnd->ableToRetaliate() + && stack->alive()) //attacker may have died (fire shield) + { + BattleAttack bat; + prepareAttack(bat, stackAtEnd, stack, 0, stack->position); + bat.flags |= BattleAttack::COUNTER; + sendAndApply(&bat); + handleAfterAttackCasting(bat); + } } //return @@ -3529,18 +3524,24 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) prepareAttack(bat2, stack, destStack, 0, ba.destinationTile); sendAndApply(&bat2); } - //TODO: allow more than one additional attack - if(stack->valOfBonuses(Bonus::ADDITIONAL_ATTACK) > 0 //if unit shots twice let's make another shot - && stack->alive() - && destStack->alive() - && stack->shots - ) + //allow more than one additional attack + + int additionalAttacks = stack->getBonuses(Selector::type (Bonus::ADDITIONAL_ATTACK), + (Selector::effectRange (Bonus::NO_LIMIT) || Selector::effectRange(Bonus::ONLY_DISTANCE_FIGHT)))->totalValue(); + for (int i = 0; i < additionalAttacks; ++i) { - BattleAttack bat; - bat.flags |= BattleAttack::SHOT; - prepareAttack(bat, stack, destStack, 0, ba.destinationTile); - sendAndApply(&bat); - handleAfterAttackCasting(bat); + if( + stack->alive() + && destStack->alive() + && stack->shots + ) + { + BattleAttack bat; + bat.flags |= BattleAttack::SHOT; + prepareAttack(bat, stack, destStack, 0, ba.destinationTile); + sendAndApply(&bat); + handleAfterAttackCasting(bat); + } } sendAndApply(&end_action);