diff --git a/CBattleInterface.cpp b/CBattleInterface.cpp index 09083b57d..fe4163d99 100644 --- a/CBattleInterface.cpp +++ b/CBattleInterface.cpp @@ -550,7 +550,7 @@ void CBattleInterface::show(SDL_Surface * to) && (LOCPLINT->curAction->destinationTile != curStack.position) //nor if it's on destination tile for current action ) ) - && !vstd::contains(curStack.abilities,SIEGE_WEAPON) //and not a war machine... + && !curStack.hasFeatureOfType(StackFeature::SIEGE_WEAPON) //and not a war machine... ) { int xAdd = curStack.attackerOwned ? 220 : 202; diff --git a/CCallback.cpp b/CCallback.cpp index a5c41f913..e06d7c385 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -533,7 +533,7 @@ bool CCallback::battleCanShoot(int ID, int dest) return false; } - if(vstd::contains(our->abilities,SHOOTER)//it's shooter + if(our->hasFeatureOfType(StackFeature::SHOOTER)//it's shooter && our->owner != dst->owner && dst->alive() && !gs->curB->isStackBlocked(ID) diff --git a/CGameState.cpp b/CGameState.cpp index 1645dfefe..69c090975 100644 --- a/CGameState.cpp +++ b/CGameState.cpp @@ -443,9 +443,44 @@ std::pair< std::vector, int > BattleInfo::getPath(int start, int dest, bool return std::make_pair(path, dist[dest]); } +int CStack::valOfFeatures(StackFeature::ECombatFeatures type, int subtype) const +{ + int ret = 0; + if(subtype == -1) + { + for(std::vector::const_iterator i=features.begin(); i != features.end(); i++) + if(i->type == type) + ret += i->value; + } + else + { + for(std::vector::const_iterator i=features.begin(); i != features.end(); i++) + if(i->type == type && i->subtype == subtype) + ret += i->value; + } + return ret; +} + +bool CStack::hasFeatureOfType(StackFeature::ECombatFeatures type, int subtype) const +{ + if(subtype == -1) //any subtype + { + for(std::vector::const_iterator i=features.begin(); i != features.end(); i++) + if(i->type == type) + return true; + } + else //given subtype + { + for(std::vector::const_iterator i=features.begin(); i != features.end(); i++) + if(i->type == type && i->subtype == subtype) + return true; + } + return false; +} + CStack::CStack(CCreature * C, int A, int O, int I, bool AO, int S) :ID(I), creature(C), amount(A), baseAmount(A), firstHPleft(C->hitPoints), owner(O), slot(S), attackerOwned(AO), position(-1), - counterAttacks(1), shots(C->shots), state(), effects(), speed(creature->speed), abilities(C->abilities), attack(C->attack), defense(C->defence) + counterAttacks(1), shots(C->shots), state(), effects(), speed(creature->speed), features(C->abilities), attack(C->attack), defense(C->defence) { state.insert(ALIVE); } @@ -577,10 +612,10 @@ si32 CStack::Defense() const bool CStack::willMove() { - return !vstd::contains(state,DEFENDING) - && !vstd::contains(state,MOVED) + return !vstd::contains(state, DEFENDING) + && !vstd::contains(state, MOVED) && alive() - && !vstd::contains(abilities,NOT_ACTIVE); //eg. Ammo Cart + && ! hasFeatureOfType(StackFeature::NOT_ACTIVE); //eg. Ammo Cart } CGHeroInstance* CGameState::HeroesPool::pickHeroFor(bool native, int player, const CTown *town, int notThatOne) @@ -2039,12 +2074,12 @@ std::vector BattleInfo::getStackQueue() int id = -1, speed = -1; for(unsigned int i=0; istate,DEFENDING)) + if((moved == 1 ||!vstd::contains(stacks[i]->state, DEFENDING)) && stacks[i]->alive() - && (moved == 1 || !vstd::contains(stacks[i]->state,MOVED)) + && (moved == 1 || !vstd::contains(stacks[i]->state, MOVED)) && !vstd::contains(stacks[i]->state,WAITING) && taken[i]==0 - && !vstd::contains(stacks[i]->abilities,NOT_ACTIVE)) //eg. Ammo Cart + && !stacks[i]->hasFeatureOfType(StackFeature::NOT_ACTIVE)) //eg. Ammo Cart { if(speed == -1 || stacks[i]->Speed() > speed) { @@ -2063,12 +2098,12 @@ std::vector BattleInfo::getStackQueue() int id = -1, speed = 10000; //infinite speed for(unsigned int i=0; istate,DEFENDING)) + if((moved == 1 ||!vstd::contains(stacks[i]->state, DEFENDING)) && stacks[i]->alive() - && (moved == 1 || !vstd::contains(stacks[i]->state,MOVED)) + && (moved == 1 || !vstd::contains(stacks[i]->state, MOVED)) && vstd::contains(stacks[i]->state,WAITING) && taken[i]==0 - && !vstd::contains(stacks[i]->abilities,NOT_ACTIVE)) //eg. Ammo Cart + && !stacks[i]->hasFeatureOfType(StackFeature::NOT_ACTIVE)) //eg. Ammo Cart { if(stacks[i]->Speed() < speed) //slowest one { diff --git a/CGameState.h b/CGameState.h index 1f0124687..0a750b1f9 100644 --- a/CGameState.h +++ b/CGameState.h @@ -158,7 +158,7 @@ public: si32 defense; //defense of stack with hero bonus si8 morale, luck; //base stack luck/morale - std::set abilities; + std::vector features; std::set state; struct StackEffect { @@ -172,8 +172,11 @@ public: }; std::vector effects; + int valOfFeatures(StackFeature::ECombatFeatures type, int subtype = -1) const;//subtype -> subtype of bonus, if -1 then any + bool hasFeatureOfType(StackFeature::ECombatFeatures type, int subtype = -1) const; //determines if stack has a bonus of given type (and optionally subtype) + CStack(CCreature * C, int A, int O, int I, bool AO, int S); //c-tor - CStack() : ID(-1), creature(NULL), amount(-1), baseAmount(-1), firstHPleft(-1), owner(255), slot(255), attackerOwned(true), position(-1), counterAttacks(1), abilities(), state(), effects() {} //c-tor + CStack() : ID(-1), creature(NULL), amount(-1), baseAmount(-1), firstHPleft(-1), owner(255), slot(255), attackerOwned(true), position(-1), counterAttacks(1) {} //c-tor const StackEffect * getEffect(ui16 id) const; //effect id (SP) ui8 howManyEffectsSet(ui16 id) const; //returns amount of effects with given id set for this stack bool willMove(); //if stack has remaining move this turn @@ -191,7 +194,7 @@ public: ui32 id; h & id; creature = &VLC->creh->creatures[id]; - abilities = creature->abilities; + features = creature->abilities; } template void serialize(Handler &h, const int version) { diff --git a/global.h b/global.h index 0d6437a96..6c62f1ec6 100644 --- a/global.h +++ b/global.h @@ -47,12 +47,101 @@ enum Ecolor {RED, BLUE, TAN, GREEN, ORANGE, PURPLE, TEAL, PINK}; //player's colo enum EvictoryConditions {artifact, gatherTroop, gatherResource, buildCity, buildGrail, beatHero, captureCity, beatMonster, takeDwellings, takeMines, transportItem, winStandard=255}; enum ElossCon {lossCastle, lossHero, timeExpires, lossStandard=255}; -enum EAbilities {DOUBLE_WIDE, FLYING, SHOOTER, TWO_HEX_ATTACK, SIEGE_ABILITY, SIEGE_WEAPON, - KING1, KING2, KING3, MIND_IMMUNITY, NO_OBSTACLE_PENALTY, NO_CLOSE_COMBAT_PENALTY, - JOUSTING, FIRE_IMMUNITY, TWICE_ATTACK, NO_ENEMY_RETALIATION, NO_MORAL_PENALTY, - UNDEAD, MULTI_HEAD_ATTACK, EXTENDED_RADIOUS_SHOOTER, GHOST, RAISES_MORALE, - LOWERS_MORALE, DRAGON, STRIKE_AND_RETURN, FEARLESS, REBIRTH, NOT_ACTIVE}; //some flags are used only for battles -enum ECombatInfo{ALIVE = NOT_ACTIVE+1, SUMMONED, CLONED, HAD_MORALE, WAITING, MOVED, DEFENDING}; +//enum EAbilities {DOUBLE_WIDE, FLYING, SHOOTER, TWO_HEX_ATTACK, SIEGE_ABILITY, SIEGE_WEAPON, +// KING1, KING2, KING3, MIND_IMMUNITY, NO_OBSTACLE_PENALTY, NO_CLOSE_COMBAT_PENALTY, +// JOUSTING, FIRE_IMMUNITY, TWICE_ATTACK, NO_ENEMY_RETALIATION, NO_MORAL_PENALTY, +// UNDEAD, MULTI_HEAD_ATTACK, EXTENDED_RADIOUS_SHOOTER, GHOST, RAISES_MORALE, +// LOWERS_MORALE, DRAGON, STRIKE_AND_RETURN, FEARLESS, REBIRTH, NOT_ACTIVE}; //some flags are used only for battles +enum ECombatInfo{ALIVE = 180, SUMMONED, CLONED, HAD_MORALE, WAITING, MOVED, DEFENDING}; + +struct StackFeature +{ + //general list of stack abilities and effects + enum ECombatFeatures + { + NO_TYPE, + DOUBLE_WIDE, FLYING, SHOOTER, CHARGE_IMMUNITY, ADDITIONAL_ATTACK, UNLIMITED_RETAILATIONS, + NO_MELEE_PENALTY, JOUSTING /*for champions*/, + RAISING_MORALE, HATE /*eg. angels hate devils*/, + KING1, + KING2, KING3, MAGIC_RESISTANCE /*in %*/, + CHANGES_SPELL_COST /*in mana points, eg. pegasus or mage*/, + SPELL_AFTER_ATTACK /* subtype - spell id, value - spell level, aditional info - chance in %; eg. dendroids*/, + SPELL_RESISTANCE_AURA /*eg. unicorns, value - resistance bonus in % for adjacent creatures*/, + LEVEL_SPELL_IMMUNITY /*creature is immune to all spell with level below value of this bonus*/, + TWO_HEX_ATTACK_BREATH /*eg. dragons*/, + SPELL_DAMAGE_REDUCTION /*eg. golems; value - reduction in %, subtype - spell school; -1 - all, 0 - air, 1 - fire, 2 - water, 3 - earth*/, + NO_WALL_PENALTY, NON_LIVING /*eg. gargoyle*/, + RANDOM_SPELLCASTER, BLOCKS_RETAILATION /*eg. naga*/, + SPELL_IMMUNITY /*value - spell id*/, + MANA_CHANNELING /*in %, eg. familiar*/, + SPELL_LIKE_ATTACK /*value - spell id; range is taken from spell, but damage from creature; eg. magog*/, + THREE_HEADED_ATTACK /*eg. cerberus*/, + DEAMON_SUMMONING /*pit lord*/, + FIRE_IMMUNITY, FIRE_SHIELD, ENEMY_MORALE_DECREASING, ENEMY_LUCK_DECREASING, UNDEAD, REGENERATION, MANA_DRAIN, LIFE_DRAIN, + DOUBLE_DAMAGE_CHANCE /*in %, eg. dread knight*/, + RETURN_AFTER_STRIKE, SELF_MORALE /*eg. minotaur*/, + SPELLCASTER /*value - spell id*/, CATAPULT, + ENEMY_DEFENCE_REDUCTION /*in %, eg. behemots*/, + GENERAL_DAMAGE_REDUCTION /*eg. while stoned or blinded - in %, subtype: -1 - any damage, 0 - melee damage, 1 - ranged damage*/, + ATTACKS_ALL_ADAJCENT /*eg. hydra*/, + MORE_DAMEGE_FROM_SPELL /*value - damage increase in %, subtype - spell id*/, + CASTS_SPELL_WHEN_KILLED /*similar to spell after attack*/, + FEAR, FEARLESS, NO_DISTANCE_PENALTY, NO_OBSTACLES_PENALTY, + SELF_LUCK /*halfling*/, + ATTACK_BONUS, DEFENCE_BONUS, SPEED_BONUS, HP_BONUS, ENCHANTER, HEALER, SIEGE_WEAPON, LUCK_BONUS, MORALE_BONUS, HYPNOTIZED, ADDITIONAL_RETAILATION, + MAGIC_MIRROR /* value - chance of redirecting in %*/, + SUMMONED, ALWAYS_MINUMUM_DAMAGE /*unit does its minimum damage from range; -1 - any attack, 0 - melee, 1 - ranged*/, + ALWAYS_MAXIMUM_DAMAGE /*eg. bless effect, -1 - any attack, 0 - melee, 1 - ranged*/, + ATTACKS_NEAREST_CREATURE /*while in berserk*/, IN_FRENZY, + SLAYER /*value - level*/, + FORGETFULL /*forgetfullnes spell effect*/, + CLONED, NOT_ACTIVE + }; + + enum EDuration + { + WHOLE_BATTLE, + N_TURNS, + UNITL_BEING_ATTACKED,/*removed after attack and counterattacks are performed*/ + UNTIL_ATTACK /*removed after attack and counterattacks are performed*/ + }; + + ECombatFeatures type; + EDuration duration; + ui16 turnsRemain; //if duration is N_TURNS it describes how long the effect will last + si16 subtype; //subtype of bonus/feature + si32 value; + si32 additionalInfo; + + inline bool operator == (const ECombatFeatures & cf) const + { + return type == cf; + } + StackFeature() : type(NO_TYPE) + {} + StackFeature(const ECombatFeatures & cf) : type(cf), duration(WHOLE_BATTLE) + {} + + template void serialize(Handler &h, const int version) + { + h & type & duration & turnsRemain & subtype & value & additionalInfo; + } +}; + +//generates StackFeature from given data +inline StackFeature makeFeature(StackFeature::ECombatFeatures type, StackFeature::EDuration duration, si16 subtype, si32 value, ui16 turnsRemain = 0, si32 additionalInfo = 0) +{ + StackFeature sf; + sf.type = type; + sf.duration = duration; + sf.turnsRemain = turnsRemain; + sf.subtype = subtype; + sf.value = value; + sf.additionalInfo = additionalInfo; + + return sf; +} class CGameInfo; extern CGameInfo* CGI; diff --git a/hch/CCreatureHandler.cpp b/hch/CCreatureHandler.cpp index 30fee1f00..5d00a97e9 100644 --- a/hch/CCreatureHandler.cpp +++ b/hch/CCreatureHandler.cpp @@ -52,21 +52,24 @@ int CCreature::getQuantityID(const int & quantity) bool CCreature::isDoubleWide() const { - return vstd::contains(abilities,DOUBLE_WIDE); + return vstd::contains(abilities, StackFeature::DOUBLE_WIDE); } bool CCreature::isFlying() const { - return vstd::contains(abilities,FLYING); + return vstd::contains(abilities, StackFeature::FLYING); } + bool CCreature::isShooting() const { - return vstd::contains(abilities,SHOOTER); + return vstd::contains(abilities, StackFeature::SHOOTER); } + bool CCreature::isUndead() const { - return vstd::contains(abilities,UNDEAD); + return vstd::contains(abilities, StackFeature::UNDEAD); } + si32 CCreature::maxAmount(const std::vector &res) const //how many creatures can be bought { int ret = 2147483645; @@ -335,19 +338,19 @@ void CCreatureHandler::loadCreatures() ncre.abilityRefs = buf.substr(befi, i-befi); i+=2; if(boost::algorithm::find_first(ncre.abilityRefs, "DOUBLE_WIDE")) - ncre.abilities.insert(DOUBLE_WIDE); + ncre.abilities.push_back(StackFeature::DOUBLE_WIDE); if(boost::algorithm::find_first(ncre.abilityRefs, "FLYING_ARMY")) - ncre.abilities.insert(FLYING); + ncre.abilities.push_back(StackFeature::FLYING); if(boost::algorithm::find_first(ncre.abilityRefs, "SHOOTING_ARMY")) - ncre.abilities.insert(SHOOTER); + ncre.abilities.push_back(StackFeature::SHOOTER); if(boost::algorithm::find_first(ncre.abilityRefs, "SIEGE_WEAPON")) - ncre.abilities.insert(SIEGE_WEAPON); + ncre.abilities.push_back(StackFeature::SIEGE_WEAPON); if(boost::algorithm::find_first(ncre.abilityRefs, "const_two_attacks")) - ncre.abilities.insert(TWICE_ATTACK); + ncre.abilities.push_back(makeFeature(StackFeature::ADDITIONAL_ATTACK, StackFeature::WHOLE_BATTLE, 0, 1)); if(boost::algorithm::find_first(ncre.abilityRefs, "const_free_attack")) - ncre.abilities.insert(NO_ENEMY_RETALIATION); + ncre.abilities.push_back(StackFeature::BLOCKS_RETAILATION); if(boost::algorithm::find_first(ncre.abilityRefs, "IS_UNDEAD")) - ncre.abilities.insert(UNDEAD); + ncre.abilities.push_back(StackFeature::UNDEAD); if(ncre.nameSing!=std::string("") && ncre.namePl!=std::string("")) { ncre.idNumber = creatures.size(); @@ -482,22 +485,22 @@ void CCreatureHandler::loadCreatures() inp2.close(); //TODO: create a tidy configuration file to control fixing unit abilities - creatures[115].abilities.insert(DOUBLE_WIDE);//water elemental should be treated as double-wide - creatures[123].abilities.insert(DOUBLE_WIDE);//ice elemental should be treated as double-wide - creatures[140].abilities.insert(DOUBLE_WIDE);//boar should be treated as double-wide - creatures[142].abilities.insert(DOUBLE_WIDE);//nomads should be treated as double-wide + creatures[115].abilities.push_back(StackFeature::DOUBLE_WIDE);//water elemental should be treated as double-wide + creatures[123].abilities.push_back(StackFeature::DOUBLE_WIDE);//ice elemental should be treated as double-wide + creatures[140].abilities.push_back(StackFeature::DOUBLE_WIDE);//boar should be treated as double-wide + creatures[142].abilities.push_back(StackFeature::DOUBLE_WIDE);//nomads should be treated as double-wide - creatures[46].abilities -= FLYING; //hell hound - creatures[47].abilities -= FLYING; //cerberus - creatures[52].abilities += FLYING; //Efreeti - creatures[53].abilities += FLYING; //Efreet Sultan + creatures[46].abilities -= StackFeature::FLYING; //hell hound + creatures[47].abilities -= StackFeature::FLYING; //cerberus + creatures[52].abilities += StackFeature::FLYING; //Efreeti + creatures[53].abilities += StackFeature::FLYING; //Efreet Sultan - creatures[47].abilities += MULTI_HEAD_ATTACK; //cerberus + creatures[47].abilities += StackFeature::THREE_HEADED_ATTACK; //cerberus - creatures[87].abilities += TWICE_ATTACK; //wolf raider + creatures[87].abilities += makeFeature(StackFeature::ADDITIONAL_ATTACK, StackFeature::WHOLE_BATTLE, 0, 1); //wolf raider - creatures[147].abilities += NOT_ACTIVE; //First Aid Tent //TODO: remove when support is added - creatures[148].abilities += NOT_ACTIVE; //Ammo Cart + creatures[147].abilities += StackFeature::NOT_ACTIVE; //First Aid Tent //TODO: remove when support is added + creatures[148].abilities += StackFeature::NOT_ACTIVE; //Ammo Cart } void CCreatureHandler::loadAnimationInfo() diff --git a/hch/CCreatureHandler.h b/hch/CCreatureHandler.h index 5aebf40e7..9ebde0b2e 100644 --- a/hch/CCreatureHandler.h +++ b/hch/CCreatureHandler.h @@ -34,7 +34,7 @@ public: std::string abilityRefs; //references to abilities, in textformat std::string animDefName; ui32 idNumber; - std::set abilities; + std::vector abilities; si8 faction; //-1 = neutral ///animation info diff --git a/hch/CObjectHandler.cpp b/hch/CObjectHandler.cpp index 8108af5fc..49f203417 100644 --- a/hch/CObjectHandler.cpp +++ b/hch/CObjectHandler.cpp @@ -879,7 +879,7 @@ bool CGHeroInstance::hasBonusOfType(HeroBonus::BonusType type, int subtype /*= - if(i->type == type && i->subtype == subtype) return true; } - throw "CGHeroInstance::hasBonusOfType - we shouln't be here!"; + return false; } int CGTownInstance::getSightRadious() const //returns sight distance diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 2cb1ba164..bc3d7e2d5 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1053,7 +1053,7 @@ void CGameHandler::checkForBattleEnd( std::vector &stacks ) hasStack[0] = hasStack[1] = false; for(int b = 0; balive() && !vstd::contains(stacks[b]->abilities,SIEGE_WEAPON)) + if(stacks[b]->alive() && !stacks[b]->hasFeatureOfType(StackFeature::SIEGE_WEAPON)) { hasStack[1-stacks[b]->attackerOwned] = true; } @@ -2093,10 +2093,10 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) sendAndApply(&bat); //counterattack - if(!vstd::contains(curStack->abilities,NO_ENEMY_RETALIATION) + if(!curStack->hasFeatureOfType(StackFeature::BLOCKS_RETAILATION) && stackAtEnd->alive() && stackAtEnd->counterAttacks - && !vstd::contains(stackAtEnd->abilities, SIEGE_WEAPON)) //TODO: support for multiple retaliatons per turn + && !stackAtEnd->hasFeatureOfType(StackFeature::SIEGE_WEAPON)) //TODO: support for multiple retaliatons per turn { prepareAttack(bat,stackAtEnd,curStack); bat.flags |= 2; @@ -2104,7 +2104,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) } //second attack - if(vstd::contains(curStack->abilities,TWICE_ATTACK) + if(curStack->valOfFeatures(StackFeature::ADDITIONAL_ATTACK) > 0 && curStack->alive() && stackAtEnd->alive() ) { @@ -2123,7 +2123,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) || !destStack //there is a stack at destination tile || !curStack->shots //stack has shots || gs->curB->isStackBlocked(curStack->ID) //we are not blocked - || !vstd::contains(curStack->abilities,SHOOTER) //our stack is shooting unit + || !curStack->hasFeatureOfType(StackFeature::SHOOTER) //our stack is shooting unit ) break; for(int g=0; geffects.size(); ++g) @@ -2139,7 +2139,7 @@ bool CGameHandler::makeBattleAction( BattleAction &ba ) bat.flags |= 1; sendAndApply(&bat); - if(vstd::contains(curStack->abilities,TWICE_ATTACK) //if unit shots twice let's make another shot + if(curStack->valOfFeatures(StackFeature::ADDITIONAL_ATTACK) > 0 //if unit shots twice let's make another shot && curStack->alive() && destStack->alive() && curStack->shots