Parcourir la source

Many fixes around bonus system. Some basic propagation mechanism (not clever but works).

Michał W. Urbańczyk il y a 14 ans
Parent
commit
4c9edd3f06

+ 2 - 2
client/CAdvmapInterface.cpp

@@ -1922,7 +1922,7 @@ void CAdvMapInt::tileHovered(const int3 &tile)
 						const CGTownInstance* townObj = dynamic_cast<const CGTownInstance*>(objAtTile);
 
 						// Show movement cursor for unguarded enemy towns, otherwise attack cursor.
-						if (townObj && !townObj->stacksCount())
+						if (townObj && !townObj->armedGarrison())
 							CCS->curh->changeGraphic(0, 9 + turns*6);
 						else
 							CCS->curh->changeGraphic(0, 5 + turns*6);
@@ -1955,7 +1955,7 @@ void CAdvMapInt::tileHovered(const int3 &tile)
 					const CGGarrison* garrObj = dynamic_cast<const CGGarrison*>(objAtTile); //TODO evil evil cast!
 
 					// Show battle cursor for guarded enemy garrisons, otherwise movement cursor.
-					if (garrObj&&  garrObj->stacksCount() 
+					if (garrObj  &&  garrObj->stacksCount() 
 						&& !LOCPLINT->cb->getPlayerRelations( LOCPLINT->playerID, garrObj->tempOwner) )
 						CCS->curh->changeGraphic(0, 5 + turns*6);
 					else

+ 1 - 3
config/cr_abils.txt

@@ -89,9 +89,7 @@
 +  66 SPELL_AFTER_ATTACK 0 42 20 	//black knights
 +  67 DOUBLE_DAMAGE_CHANCE 20 0 0	//vampire lords
 +  67 SPELL_AFTER_ATTACK 0 42 20 	//dread knights
-+  68 ENEMY_MORALE_DECREASING -1 0 0		//bone dragon
 +  68 DRAGON_NATURE 0 0 0			//bone dragon is a dragon
-+  69 ENEMY_MORALE_DECREASING -1 0 0 		//ghost dragon
 +  69 SPELL_AFTER_ATTACK 0 75 20   	//ghost dragon
 +  69 DRAGON_NATURE 0 0 0			//ghost dragon is a dragon
 +  70 SPELL_IMMUNITY 0 62 0	  	   	//troglodytes are immune to blind
@@ -190,4 +188,4 @@
 -  47 FLYING			  			//cerberus doesn't fly
 - 120 DOUBLE_WIDE					//psychic elemental
 - 121 DOUBLE_WIDE					//magic elemental
-0
+0

+ 27 - 56
lib/BattleState.cpp

@@ -1100,27 +1100,6 @@ si8 BattleInfo::canTeleportTo(const CStack * stack, THex destHex, int telportLev
 
 }
 
-// void BattleInfo::getBonuses(BonusList &out, const CSelector &selector, const CBonusSystemNode *root /*= NULL*/) const
-// {
-// 	CBonusSystemNode::getBonuses(out, selector, root);
-// 
-// 	const CStack *dest = dynamic_cast<const CStack*>(root);
-// 	if (!dest)
-// 		return;
-// 
-// 	//TODO: make it in clean way
-// 	if(Selector::matchesType(selector, Bonus::MORALE) || Selector::matchesType(selector, Bonus::LUCK))
-// 	{
-// 		BOOST_FOREACH(const CStack *s, stacks)
-// 		{
-// 			if(s->owner == dest->owner)
-// 				s->getBonuses(out, selector, Selector::effectRange(Bonus::ONLY_ALLIED_ARMY), this);
-// 			else
-// 				s->getBonuses(out, selector, Selector::effectRange(Bonus::ONLY_ENEMY_ARMY), this);
-// 		}
-// 	}
-// }
-
 bool BattleInfo::battleCanShoot(const CStack * stack, THex dest) const
 {
 	const CStack *dst = getStackT(dest);
@@ -1223,7 +1202,9 @@ si8 BattleInfo::battleMaxSpellLevel() const
 void BattleInfo::localInit()
 {
 	belligerents[0]->battle = belligerents[1]->battle = this;
-	//TODO: attach battle to belligerents
+	
+	BOOST_FOREACH(CArmedInstance *b, belligerents)
+		b->attachTo(this);
 
 	BOOST_FOREACH(CStack *s, stacks)
 	{
@@ -1492,39 +1473,6 @@ BattleInfo * BattleInfo::setupBattle( int3 tile, int terrain, int terType, const
 		}
 	}
 
-	// 	//giving building bonuses, if siege and we have harrisoned hero
-	// 	if (town)
-	// 	{
-	// 		if (heroes[1])
-	// 		{
-	// 			for (int i=0; i<4; i++)
-	// 			{
-	// 				int val = town->defenceBonus(i);
-	// 				if (val)
-	// 				{
-	// 					GiveBonus gs;
-	// 					gs.bonus = Bonus(Bonus::ONE_BATTLE, Bonus::PRIMARY_SKILL, Bonus::OBJECT, val, -1, "", i);
-	// 					gs.id = heroes[1]->id;
-	// 					sendAndApply(&gs);
-	// 				}
-	// 			}
-	// 		}
-	// 		else//if we don't have hero - apply separately, if hero present - will be taken from hero bonuses
-	// 		{
-	// 			if(town->subID == 0  &&  vstd::contains(town->builtBuildings,22)) //castle, brotherhood of sword built
-	// 				for(int g=0; g<stacks.size(); ++g)
-	// 					stacks[g]->addNewBonus(makeFeature(Bonus::MORALE, Bonus::ONE_BATTLE, 0, 2, Bonus::TOWN_STRUCTURE));
-	// 
-	// 			else if(vstd::contains(town->builtBuildings,5)) //tavern is built
-	// 				for(int g=0; g<stacks.size(); ++g)
-	// 					stacks[g]->addNewBonus(makeFeature(Bonus::MORALE, Bonus::ONE_BATTLE, 0, 1, Bonus::TOWN_STRUCTURE));
-	// 
-	// 			if(town->subID == 1  &&  vstd::contains(town->builtBuildings,21)) //rampart, fountain of fortune is present
-	// 				for(int g=0; g<stacks.size(); ++g)
-	// 					stacks[g]->addNewBonus(makeFeature(Bonus::LUCK, Bonus::ONE_BATTLE, 0, 2, Bonus::TOWN_STRUCTURE));
-	// 		}
-	// 	}
-
 	//giving terrain overalay premies
 	int bonusSubtype = -1;
 	switch(terType)
@@ -1586,12 +1534,13 @@ BattleInfo * BattleInfo::setupBattle( int3 tile, int terrain, int terType, const
 	if(town) //during siege always take premies for native terrain of faction
 		terrain = VLC->heroh->nativeTerrains[town->town->typeID];
 
-	ILimiter *nativeTerrain = new CreatureNativeTerrainLimiter(terrain);
+	boost::shared_ptr<ILimiter> nativeTerrain(new CreatureNativeTerrainLimiter(terrain));
 	curB->addNewBonus(makeFeature(Bonus::STACKS_SPEED, Bonus::ONE_BATTLE, 0, 1, Bonus::TERRAIN_NATIVE)->addLimiter(nativeTerrain));
 	curB->addNewBonus(makeFeature(Bonus::PRIMARY_SKILL, Bonus::ONE_BATTLE, PrimarySkill::ATTACK, 1, Bonus::TERRAIN_NATIVE)->addLimiter(nativeTerrain));
 	curB->addNewBonus(makeFeature(Bonus::PRIMARY_SKILL, Bonus::ONE_BATTLE, PrimarySkill::DEFENSE, 1, Bonus::TERRAIN_NATIVE)->addLimiter(nativeTerrain));
 	//////////////////////////////////////////////////////////////////////////
 
+	//tactics
 	int tacticLvls[2] = {0};
 	for(int i = 0; i < ARRAY_COUNT(tacticLvls); i++)
 	{
@@ -1607,6 +1556,28 @@ BattleInfo * BattleInfo::setupBattle( int3 tile, int terrain, int terType, const
 	else
 		curB->tacticDistance = 0;
 
+
+	// workaround — bonuses affecting only enemy
+	for(int i = 0; i < 2; i++)
+	{
+		TNodes nodes;
+		curB->belligerents[i]->getRedAncestors(nodes);
+		BOOST_FOREACH(CBonusSystemNode *n, nodes)
+		{
+			BOOST_FOREACH(Bonus *b, n->exportedBonuses)
+			{
+				if(b->effectRange == Bonus::ONLY_ENEMY_ARMY/* && b->propagator && b->propagator->shouldBeAttached(curB)*/)
+				{
+					Bonus *bCopy = new Bonus(*b);
+					bCopy->effectRange = Bonus::NO_LIMIT;
+					bCopy->propagator.reset();
+					bCopy->limiter.reset(new StackOwnerLimiter(curB->sides[!i]));
+					curB->addNewBonus(bCopy);
+				}
+			}
+		}
+	}
+
 	return curB;
 }
 

+ 1 - 1
lib/CArtHandler.cpp

@@ -496,7 +496,7 @@ void CArtHandler::giveArtBonus( int aid, Bonus::BonusType type, int val, int sub
 	added->valType = valType;
 	added->limiter.reset(limiter);
 	if(type == Bonus::MORALE || Bonus::LUCK)
-		added->description = "\n" + artifacts[aid]->Name()  + (val > 0 ? " +" : " ") + boost::lexical_cast<std::string>(val);
+		added->description = artifacts[aid]->Name()  + (val > 0 ? " +" : " ") + boost::lexical_cast<std::string>(val);
 	artifacts[aid]->addNewBonus(added);
 }
 

+ 1 - 1
lib/CCreatureHandler.cpp

@@ -329,7 +329,7 @@ void CCreatureHandler::loadCreatures()
 			if(boost::algorithm::find_first(ncre.abilityRefs, "const_raises_morale"))
 			{
 				ncre.addBonus(+1, Bonus::MORALE);;
-				ncre.bonuses.back()->effectRange = Bonus::ONLY_ALLIED_ARMY;
+				ncre.bonuses.back()->addPropagator(new CPropagatorNodeType(CBonusSystemNode::HERO));
 			}
 			if(boost::algorithm::find_first(ncre.abilityRefs, "const_lowers_morale"))
 			{

+ 6 - 1
lib/CGameState.cpp

@@ -3010,7 +3010,7 @@ PlayerState::PlayerState()
  : color(-1), currentSelection(0xffffffff), enteredWinningCheatCode(0), 
    enteredLosingCheatCode(0), status(INGAME), daysWithoutCastle(0)
 {
-
+	nodeType = PLAYER;
 }
 
 std::string PlayerState::nodeName() const
@@ -3129,3 +3129,8 @@ DuelParameters::DuelParameters()
 	terType = TerrainTile::dirt;
 	bfieldType = 15;
 }
+
+TeamState::TeamState()
+{
+	nodeType = TEAM;
+}

+ 1 - 1
lib/CGameState.h

@@ -167,7 +167,7 @@ public:
 	std::set<ui8> players; // members of this team
 	std::vector<std::vector<std::vector<ui8> > >  fogOfWarMap; //true - visible, false - hidden
 	
-	//TeamState();
+	TeamState();
 	
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{

+ 110 - 164
lib/CObjectHandler.cpp

@@ -977,10 +977,12 @@ void CGHeroInstance::initObj()
 					bonus->subtype = PrimarySkill::ATTACK;
 					speciality.addNewBonus(bonus);
 
+					bonus = new Bonus(*bonus);
 					bonus->subtype = PrimarySkill::DEFENSE;
 					speciality.addNewBonus(bonus);
 					//values will be calculated later
 
+					bonus = new Bonus(*bonus);
 					bonus->type = Bonus::STACKS_SPEED;
 					bonus->val = 1; //+1 speed
 					speciality.addNewBonus(bonus);
@@ -993,6 +995,8 @@ void CGHeroInstance::initObj()
 				bonus->subtype = it->subtype; //skill id
 				bonus->val = it->val; //value per level, in percent
 				speciality.addNewBonus(bonus);
+				bonus = new Bonus(*bonus);
+
 				switch (it->additionalinfo)
 				{
 					case 0: //normal
@@ -1068,13 +1072,16 @@ void CGHeroInstance::initObj()
 				bonus->subtype = it->subtype; //base id
 				bonus->additionalInfo = it->additionalinfo; //target id
 				speciality.addNewBonus(bonus);
+				bonus = new Bonus(*bonus);
 
 				for (std::set<ui32>::iterator i = (*creatures)[it->subtype]->upgrades.begin();
 					i != (*creatures)[it->subtype]->upgrades.end(); i++)
 				{
 					bonus->subtype = *i; //propagate for regular upgrades of base creature
 					speciality.addNewBonus(bonus);
+					bonus = new Bonus(*bonus);
 				}
+				delNull(bonus);
 				break;
 			}
 			case 10://resource generation
@@ -1162,7 +1169,7 @@ void CGHeroInstance::UpdateSpeciality()
 void CGHeroInstance::updateSkill(int which, int val)
 {
 	if(which == LEADERSHIP || which == LUCK)
-	{
+	{ //luck-> VLC->generaltexth->arraytxt[73+luckSkill]; VLC->generaltexth->arraytxt[104+moraleSkill]
 		bool luck = which == LUCK;
 		Bonus::BonusType type[] = {Bonus::MORALE, Bonus::LUCK};
 
@@ -1445,60 +1452,6 @@ void CGHeroInstance::pushPrimSkill(int which, int val)
 	addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::PRIMARY_SKILL, Bonus::HERO_BASE_SKILL, val, id, which));
 }
 
-// void CGHeroInstance::getBonuses(BonusList &out, const CSelector &selector, const CBonusSystemNode *root /*= NULL*/) const
-// {
-// #define FOREACH_OWNER_TOWN(town) if(const PlayerState *p = cb->getPlayerState(tempOwner)) BOOST_FOREACH(const CGTownInstance *town, p->towns)
-// 
-// 	CArmedInstance::getBonuses(out, selector, root); ///that's not part of macro!
-// 
-// 	//TODO eliminate by moving secondary skills effects to bonus system
-// 	if(Selector::matchesType(selector, Bonus::LUCK))
-// 	{
-// 		//luck skill
-// 		if(int luckSkill = getSecSkillLevel(9)) 
-// 			out.push_back(Bonus(Bonus::PERMANENT, Bonus::LUCK, Bonus::SECONDARY_SKILL, luckSkill, 9, VLC->generaltexth->arraytxt[73+luckSkill]));
-// 
-// 		//guardian spirit
-// 		FOREACH_OWNER_TOWN(t)
-// 			if(t->subID ==1 && vstd::contains(t->builtBuildings,26)) //rampart with grail
-// 				out.push_back(Bonus(Bonus::PERMANENT, Bonus::LUCK, Bonus::TOWN_STRUCTURE, +2, 26, VLC->generaltexth->buildings[1][26].first + " +2"));
-// 	}
-// 
-// 	if(Selector::matchesType(selector, Bonus::SEA_MOVEMENT))
-// 	{
-// 		//lighthouses
-// 		FOREACH_OWNER_TOWN(t)
-// 			if(t->subID == 0 && vstd::contains(t->builtBuildings,17)) //castle
-// 				out.push_back(Bonus(Bonus::PERMANENT, Bonus::SEA_MOVEMENT, Bonus::TOWN_STRUCTURE, +500, 17, VLC->generaltexth->buildings[0][17].first + " +500"));
-// 	}
-// 
-// 	if(Selector::matchesType(selector, Bonus::MORALE))
-// 	{
-// 		//leadership
-// 		if(int moraleSkill = getSecSkillLevel(6)) 
-// 			out.push_back(Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::SECONDARY_SKILL, moraleSkill, 6, VLC->generaltexth->arraytxt[104+moraleSkill]));
-// 
-// 		//colossus
-// 		FOREACH_OWNER_TOWN(t)
-// 			if(t->subID == 0 && vstd::contains(t->builtBuildings,26)) //castle
-// 				out.push_back(Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::TOWN_STRUCTURE, +2, 26, VLC->generaltexth->buildings[0][26].first + " +2"));
-// 	}
-// 
-// 	if(Selector::matchesTypeSubtype(selector, Bonus::SECONDARY_SKILL_PREMY, 12)) //necromancy
-// 	{
-// 		FOREACH_OWNER_TOWN(t)
-// 		{
-// 			if(t->subID == 4) //necropolis
-// 			{
-// 				if(vstd::contains(t->builtBuildings,21)) //necromancy amplifier
-// 					out.push_back(Bonus(Bonus::PERMANENT, Bonus::SECONDARY_SKILL_PREMY, Bonus::TOWN_STRUCTURE, +10, 21, VLC->generaltexth->buildings[4][21].first + " +10%", 12));
-// 				if(vstd::contains(t->builtBuildings,26)) //grail - Soul prison
-// 					out.push_back(Bonus(Bonus::PERMANENT, Bonus::SECONDARY_SKILL_PREMY, Bonus::TOWN_STRUCTURE, +20, 26, VLC->generaltexth->buildings[4][26].first + " +20%", 12));
-// 			}
-// 		}
-// 	}
-// }
-
 EAlignment CGHeroInstance::getAlignment() const
 {
 	return type->heroClass->getAlignment();
@@ -1984,39 +1937,6 @@ int CGTownInstance::spellsAtLevel(int level, bool checkGuild) const
 	return ret;
 }
 
-int CGTownInstance::defenceBonus(int type) const
-{
-	int ret=0;
-	switch (type)
-		{
-/*attack*/		case 0: 
-						if (subID == 6 && vstd::contains(builtBuildings,26))//Stronghold, grail
-							ret += 12;
-						if (subID == 7 && vstd::contains(builtBuildings,26))//Fortress, grail
-							ret += 10;
-						if (subID == 7 && vstd::contains(builtBuildings,22))//Fortress, Blood Obelisk
-							ret += 2;
-							return ret;
-/*defence*/		case 1:
-						if (subID == 7 && vstd::contains(builtBuildings,21))//Fortress, Glyphs of Fear
-							ret += 2;
-						if (subID == 7 && vstd::contains(builtBuildings,26))//Fortress, Grail
-							ret += 10;
-							return ret;
-/*spellpower*/	case 2:
-						if (subID == 3 && vstd::contains(builtBuildings,21))//Inferno, Brimstone Clouds
-							ret += 2;
-						if (subID == 5 && vstd::contains(builtBuildings,26))//Dungeon, Grail
-							ret += 12;
-							return ret;
-/*knowledge*/	case 3:
-						if (subID == 2 && vstd::contains(builtBuildings,26))//Tower, Grail
-							ret += 15;
-							return ret;
-		}
-		return 0;//Why we are here? wrong type?
-}
-
 bool CGTownInstance::needsLastStack() const
 {
 	if(garrisonHero)
@@ -2028,25 +1948,30 @@ void CGTownInstance::onHeroVisit(const CGHeroInstance * h) const
 {
 	if( !cb->gameState()->getPlayerRelations( getOwner(), h->getOwner() ))//if this is enemy
 	{
-		if(stacksCount() > 0 || visitingHero)
+		if(armedGarrison() || visitingHero)
 		{
 			const CGHeroInstance *defendingHero = NULL;
+			const CArmedInstance *defendingArmy = this;
+
 			if(visitingHero)
 				defendingHero = visitingHero;
 			else if(garrisonHero)
 				defendingHero = garrisonHero;
 
-			const CArmedInstance *defendingArmy = this;
 			if(defendingHero)
 				defendingArmy = defendingHero;
 
 			bool outsideTown = (defendingHero == visitingHero && garrisonHero);
+
+			//TODO 
+			//"borrowing" army from garrison to visiting hero 
+
 			cb->startBattleI(h, defendingArmy, getSightCenter(), h, defendingHero, false, boost::bind(&CGTownInstance::fightOver, this, h, _1), (outsideTown ? NULL : this));
 		}
 		else
 		{
 			cb->setOwner(id, h->tempOwner);
-			removeCapitols (h->getOwner());
+			removeCapitols(h->getOwner());
 			cb->heroVisitCastle(id, h->id);
 		}
 	}
@@ -2172,7 +2097,7 @@ int3 CGTownInstance::getSightCenter() const
 
 ui8 CGTownInstance::getPassableness() const
 {
-	if ( !stacksCount() )//empty castle - anyone can visit
+	if (!armedGarrison())//empty castle - anyone can visit
 		return ALL_PLAYERS;
 	if ( tempOwner == 255 )//neutral guarded - noone can visit
 		return 0;
@@ -2309,18 +2234,85 @@ void CGTownInstance::deserializationFix()
 
 void CGTownInstance::recreateBuildingsBonuses()
 {
-	bonuses.remove_if(Selector::sourceType(Bonus::TOWN_STRUCTURE)); //TODO memory leak
+	BonusList bl;
+	exportedBonuses.getBonuses(bl, Selector::sourceType(Bonus::TOWN_STRUCTURE));
+	BOOST_FOREACH(Bonus *b, bl)
+		removeBonus(b);
 
-	if(subID == 4 && vstd::contains(builtBuildings, 17))
-		addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::DARKNESS, Bonus::TOWN_STRUCTURE, 20, 17));
 
-	if(subID == 1  &&  vstd::contains(builtBuildings,21)) //rampart, fountain of fortune
-	 	addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::LUCK, Bonus::TOWN_STRUCTURE, +2, 21, VLC->generaltexth->buildings[1][21].first + " +2"));
+	if(subID != 0 || !addBonusIfBuilt(22, Bonus::MORALE, +2)) //tricky! -> checks tavern only if no bratherhood of sword or not a castle
+		addBonusIfBuilt(5, Bonus::MORALE, +1);
+	
+	if(subID == 0) //castle
+	{
+		addBonusIfBuilt(17, Bonus::SEA_MOVEMENT, +500, new CPropagatorNodeType(PLAYER)); //lighthouses
+		addBonusIfBuilt(26, Bonus::MORALE, +2, new CPropagatorNodeType(PLAYER)); //colossus
+	}
+	else if(subID == 1) //rampart
+	{
+		addBonusIfBuilt(21, Bonus::LUCK, +2); //fountain of fortune
+		addBonusIfBuilt(21, Bonus::LUCK, +2, new CPropagatorNodeType(PLAYER)); //guardian spirit
+	}
+	else if(subID == 2) //tower
+	{
+		addBonusIfBuilt(26, Bonus::PRIMARY_SKILL, +15, PrimarySkill::KNOWLEDGE); //grail
+	}
+	else if(subID == 3) //Inferno
+	{
+		addBonusIfBuilt(21, Bonus::PRIMARY_SKILL, +2, PrimarySkill::SPELL_POWER); //Brimstone Clouds
+	}
+	else if(subID == 4) //necropolis
+	{
+		addBonusIfBuilt(17, Bonus::DARKNESS, +20);
+		addBonusIfBuilt(21, Bonus::SECONDARY_SKILL_PREMY, +10, new CPropagatorNodeType(PLAYER), CGHeroInstance::NECROMANCY); //necromancy amplifier
+		addBonusIfBuilt(26, Bonus::SECONDARY_SKILL_PREMY, +20, new CPropagatorNodeType(PLAYER), CGHeroInstance::NECROMANCY); //Soul prison
+	}
+	else if(subID == 5) //Dungeon
+	{
+		addBonusIfBuilt(26, Bonus::PRIMARY_SKILL, +12, PrimarySkill::SPELL_POWER); //grail
+	}
+	else if(subID == 6) //Stronghold
+	{
+		addBonusIfBuilt(26, Bonus::PRIMARY_SKILL, +20, PrimarySkill::ATTACK); //grail
+	}
+	else if(subID == 7) //Fortress
+	{
+		addBonusIfBuilt(21, Bonus::PRIMARY_SKILL, +2, PrimarySkill::DEFENSE); //Glyphs of Fear
+		addBonusIfBuilt(22, Bonus::PRIMARY_SKILL, +2, PrimarySkill::ATTACK); //Blood Obelisk
+		addBonusIfBuilt(26, Bonus::PRIMARY_SKILL, +10, PrimarySkill::ATTACK); //grail
+		addBonusIfBuilt(26, Bonus::PRIMARY_SKILL, +10, PrimarySkill::DEFENSE); //grail
+	}
+	else if(subID == 8)
+	{
 
-	if(subID == 0  &&  vstd::contains(builtBuildings,22)) //castle, brotherhood of sword built
-	 	addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::TOWN_STRUCTURE, +2, 22, VLC->generaltexth->buildings[0][22].first + " +2"));
-	else if(vstd::contains(builtBuildings,5)) //tavern is built
-	 	addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::TOWN_STRUCTURE, +1, 5, VLC->generaltexth->buildings[0][5].first + " +1"));
+	}
+}
+
+bool CGTownInstance::addBonusIfBuilt(int building, int type, int val, int subtype /*= -1*/)
+{
+	return addBonusIfBuilt(building, type, val, NULL, subtype);
+}
+
+bool CGTownInstance::addBonusIfBuilt(int building, int type, int val, IPropagator *prop, int subtype /*= -1*/)
+{
+	if(vstd::contains(builtBuildings, building))
+	{
+		std::ostringstream descr;
+		descr << VLC->generaltexth->buildings[subID][building].first << " ";
+		if(val > 0)
+			descr << "+";
+		else if(val < 0)
+			descr << "-";
+		descr << val;
+
+		Bonus *b = new Bonus(Bonus::PERMANENT, type, Bonus::TOWN_STRUCTURE, val, building, descr.str(), subtype);
+		if(prop)
+			b->addPropagator(prop);
+		addNewBonus(b);
+		return true;
+	}
+
+	return false;
 }
 
 void CGTownInstance::setVisitingHero(CGHeroInstance *h)
@@ -2370,6 +2362,11 @@ void CGTownInstance::setGarrisonedHero(CGHeroInstance *h)
 	}
 }
 
+bool CGTownInstance::armedGarrison() const
+{
+	return stacksCount() || garrisonHero;
+}
+
 void CGVisitableOPH::onHeroVisit( const CGHeroInstance * h ) const
 {
 	if(visitors.find(h->id)==visitors.end())
@@ -3099,20 +3096,6 @@ void CGCreature::joinDecision(const CGHeroInstance *h, int cost, ui32 accept) co
 			cb->giveResource(h->tempOwner,6,-cost);
 
 		cb->tryJoiningArmy(this, h, true, false);
-// 		int slot = h->getSlotFor(subID);
-// 		if(slot >= 0) //there is place
-// 		{
-// 			//add creatures
-// 			SetGarrisons sg;
-// 			sg.garrs[h->id] = h->getArmy();
-// 			sg.garrs[h->id].addToSlot(slot, subID, getStackCount(0));
-// 			cb->sendAndApply(&sg);
-// 			cb->removeObject(id);
-// 		}
-// 		else
-// 		{
-// 			cb->showGarrisonDialog(id,h->id,true,boost::bind(&IGameCallback::removeObject,cb,id)); //show garrison window and remove ourselves from map when player ends
-// 		}
 	}
 }
 
@@ -4764,6 +4747,7 @@ void CGMagicSpring::onHeroVisit(const CGHeroInstance * h) const
 	iw.text << std::pair<ui8,ui32>(11,messageID); 
 	cb->showInfoDialog(&iw); 
 } 
+
 const std::string & CGMagicSpring::getHoverText() const 
 { 
 	hoverName = VLC->generaltexth->names[ID];
@@ -6530,13 +6514,6 @@ void CGLighthouse::giveBonusTo( ui8 player ) const
 	cb->sendAndApply(&gb);
 }
 
-CCreatureSet& CArmedInstance::getArmy() const
-{ //do not return itself by value, or it will xplode
-//	CCreatureSet set = *this; return set;
-	//WARNING! A DIRTY CONST_CAST! TO BE INVESTIGATED AND PROBABLY REMOVED!
-	return *(const_cast<CArmedInstance*>(this));
-}
-
 void CArmedInstance::randomizeArmy(int type)
 {
 	int max = VLC->creh->creatures.size();
@@ -6558,41 +6535,11 @@ void CArmedInstance::randomizeArmy(int type)
 	return;
 }
 
-// void CArmedInstance::getParents(TCNodes &out, const CBonusSystemNode *root /*= NULL*/) const
-// {
-// 	/* //already given via PlayerState->getBonuses();
-// 	const PlayerState *p = cb->getPlayerState(tempOwner);
-// 	if (p && p != root) 
-// 		out.insert(p); 
-// 	*/
-// 	out.insert(&cb->gameState()->globalEffects); //global effects are always active I believe
-// 
-// 	if(battle)
-// 		out.insert(battle);
-// }
-
 CArmedInstance::CArmedInstance()
 {
 	battle = NULL;
 }
 
-// void CArmedInstance::getBonuses(BonusList &out, const CSelector &selector, const CBonusSystemNode *root /*= NULL*/) const
-// {
-// 	CBonusSystemNode::getBonuses(out, selector, root);
-// 
-// 	if(!battle)
-// 	{
-// 		//TODO do it clean, unify with BattleInfo version
-// 		if(Selector::matchesType(selector, Bonus::MORALE) || Selector::matchesType(selector, Bonus::LUCK))
-// 		{
-// 			for(TSlots::const_iterator i=Slots().begin(); i!=Slots().end(); i++)
-// 				i->second.getBonuses(out, selector, Selector::effectRange(Bonus::ONLY_ALLIED_ARMY), this);
-// 		}
-// 	}
-// 
-
-// }
-
 int CArmedInstance::valOfGlobalBonuses(CSelector selector) const
 {
 	//if (tempOwner != NEUTRAL_PLAYER)
@@ -6640,8 +6587,17 @@ void CArmedInstance::updateMoraleBonusFromArmy()
 		b->description = boost::str(boost::format(VLC->generaltexth->arraytxt[114]) % factions.size() % b->val); //Troops of %d alignments %d
 	}
 	 
-// 	if(vstd::contains(factions,4))
-// 	 	out.push_back(Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, -1, id, VLC->generaltexth->arraytxt[116]));//Undead in group -1
+	//-1 modifier for any Necropolis unit in army
+	const ui8 UNDEAD_MODIFIER_ID = -2;
+	Bonus *undeadModifier = bonuses.getFirst(Selector::source(Bonus::ARMY, UNDEAD_MODIFIER_ID));
+ 	if(vstd::contains(factions,4))
+	{
+		if(!undeadModifier)
+			addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, -1, UNDEAD_MODIFIER_ID, VLC->generaltexth->arraytxt[116]));
+	}
+	else if(undeadModifier)
+		removeBonus(undeadModifier);
+	
 }
 
 void CArmedInstance::armyChanged()
@@ -6963,16 +6919,6 @@ CArtifactInstance* CArtifactSet::getArt(ui16 pos)
 {
 	return const_cast<CArtifactInstance*>((const_cast<const CArtifactSet*>(this))->getArt(pos));
 }
-// if(pos<19)
-// 	if(vstd::contains(artifWorn,pos))
-// 		return artifWorn.find(pos)->second;
-// 	else
-// 		return NULL;
-// else
-// 	if(pos-19 < artifacts.size())
-// 		return artifacts[pos-19];
-// 	else 
-// 		return NULL;
 
 si32 CArtifactSet::getArtPos(int aid, bool onlyWorn /*= true*/) const
 {

+ 3 - 2
lib/CObjectHandler.h

@@ -225,7 +225,6 @@ class DLL_EXPORT CArmedInstance: public CGObjectInstance, public CBonusSystemNod
 public:
 	BattleInfo *battle; //set to the current battle, if engaged
 
-	CCreatureSet& getArmy() const;
 	void randomizeArmy(int type);
 	void updateMoraleBonusFromArmy();
 
@@ -604,6 +603,8 @@ public:
 	std::string nodeName() const OVERRIDE;
 	void deserializationFix();
 	void recreateBuildingsBonuses();
+	bool addBonusIfBuilt(int building, int type, int val, IPropagator *prop, int subtype = -1); //returns true if building is built and bonus has been added
+	bool addBonusIfBuilt(int building, int type, int val, int subtype = -1); //convienence version of above
 	void setVisitingHero(CGHeroInstance *h);
 	void setGarrisonedHero(CGHeroInstance *h);
 // 	void getParents(TCNodes &out, const CBonusSystemNode *root = NULL) const;
@@ -635,7 +636,7 @@ public:
 	int dailyIncome() const; //calculates daily income of this town
 	int spellsAtLevel(int level, bool checkGuild) const; //levels are counted from 1 (1 - 5)
 	void removeCapitols (ui8 owner) const;
-	int defenceBonus(int type) const;//primary skills bonuses for defending hero
+	bool armedGarrison() const; //true if town has creatures in garrison or garrisoned hero
 
 	CGTownInstance();
 	virtual ~CGTownInstance();

+ 226 - 38
lib/HeroBonus.cpp

@@ -13,8 +13,10 @@
 #include "CGeneralTextHandler.h"
 #include "BattleState.h"
 
-#define FOREACH_CONST_PARENT(pname) 	TCNodes parents; getParents(parents); BOOST_FOREACH(const CBonusSystemNode *pname, parents)
-#define FOREACH_PARENT(pname) 	TNodes parents; getParents(parents); BOOST_FOREACH(CBonusSystemNode *pname, parents)
+#define FOREACH_CONST_PARENT(pname) 	TCNodes lparents; getParents(lparents); BOOST_FOREACH(const CBonusSystemNode *pname, lparents)
+#define FOREACH_PARENT(pname) 	TNodes lparents; getParents(lparents); BOOST_FOREACH(CBonusSystemNode *pname, lparents)
+#define FOREACH_RED_CHILD(pname) 	TNodes lchildren; getRedChildren(lchildren); BOOST_FOREACH(CBonusSystemNode *pname, lchildren)
+#define FOREACH_RED_PARENT(pname) 	TNodes lparents; getRedParents(lparents); BOOST_FOREACH(CBonusSystemNode *pname, lparents)
 
 #define BONUS_NAME(x) ( #x, Bonus::x )
 	DLL_EXPORT const std::map<std::string, int> bonusNameMap = boost::assign::map_list_of BONUS_LIST;
@@ -124,12 +126,6 @@ void DLL_EXPORT BonusList::getBonuses(BonusList &out, const CSelector &selector,
 			out.push_back(i);
 }
 
-
-void DLL_EXPORT BonusList::removeSpells(Bonus::BonusSource sourceType)
-{
-	remove_if(Selector::sourceType(sourceType));
-}
-
 void BonusList::limit(const CBonusSystemNode &node)
 {
 	remove_if(boost::bind(&CBonusSystemNode::isLimitedOnUs, boost::ref(node), _1));
@@ -338,19 +334,20 @@ CBonusSystemNode::~CBonusSystemNode()
 		while(children.size())
 			children.front()->detachFrom(this);
 	}
+
+	BOOST_FOREACH(Bonus *b, exportedBonuses)
+		delete b;
 }
 
 void CBonusSystemNode::attachTo(CBonusSystemNode *parent)
 {
 	assert(!vstd::contains(parents, parent));
 	parents.push_back(parent);
-// 	BOOST_FOREACH(Bonus *b, exportedBonuses)
-// 		propagateBonus(b);
-// 
-// 	if(parent->weActAsBonusSourceOnly())
-// 	{
-// 
-// 	}
+
+	if(parent->actsAsBonusSourceOnly())
+		parent->newRedDescendant(this);
+	else
+		newRedDescendant(parent);
 
 	parent->newChildAttached(this);
 }
@@ -358,16 +355,18 @@ void CBonusSystemNode::attachTo(CBonusSystemNode *parent)
 void CBonusSystemNode::detachFrom(CBonusSystemNode *parent)
 {
 	assert(vstd::contains(parents, parent));
-	parents -= parent;
-	 //unpropagate bonus
 
+	if(parent->actsAsBonusSourceOnly())
+		parent->removedRedDescendant(this);
+	else
+		removedRedDescendant(parent);
+
+	parents -= parent;
 	parent->childDetached(this);
 }
 
 void CBonusSystemNode::popBonuses(const CSelector &s)
 {
-	//TODO
-	//prop
 	BonusList bl;
 	exportedBonuses.getBonuses(bl, s);
 	BOOST_FOREACH(Bonus *b, bl)
@@ -384,24 +383,22 @@ void CBonusSystemNode::popBonuses(const CSelector &s)
 
 void CBonusSystemNode::addNewBonus(Bonus *b)
 {
+	assert(!vstd::contains(exportedBonuses,b));
 	exportedBonuses.push_back(b);
-	propagateBonus(b);
+	if(b->propagator)
+		propagateBonus(b);
+	else
+		bonuses.push_back(b);
 }
 
 void CBonusSystemNode::removeBonus(Bonus *b)
 {
 	exportedBonuses -= b;
-	CBonusSystemNode *whereIsOurBonus = whereToPropagate(b);
-	whereIsOurBonus->bonuses -= b;
-	delNull(b);
-}
-
-CBonusSystemNode * CBonusSystemNode::whereToPropagate(Bonus *b)
-{
 	if(b->propagator)
-		return b->propagator->getDestNode(this);
+		unpropagateBonus(b);
 	else
-		return this;
+		bonuses -= b;
+	delNull(b);
 }
 
 bool CBonusSystemNode::isLimitedOnUs(Bonus *b) const
@@ -409,7 +406,7 @@ bool CBonusSystemNode::isLimitedOnUs(Bonus *b) const
 	return b->limiter && b->limiter->limit(b, *this);
 }
 
-bool CBonusSystemNode::weActAsBonusSourceOnly() const
+bool CBonusSystemNode::actsAsBonusSourceOnly() const
 {
 	switch(nodeType)
 	{
@@ -422,14 +419,28 @@ bool CBonusSystemNode::weActAsBonusSourceOnly() const
 	}
 }
 
-TNodesVector & CBonusSystemNode::nodesOnWhichWePropagate()
+void CBonusSystemNode::propagateBonus(Bonus * b)
 {
-	return weActAsBonusSourceOnly() ? children : parents;
+	if(b->propagator->shouldBeAttached(this))
+	{
+		bonuses.push_back(b);
+		BONUS_LOG_LINE("#$# " << b->Description() << " #propagated to# " << nodeName());
+	}
+
+	FOREACH_RED_CHILD(child)
+		child->propagateBonus(b);
 }
 
-void CBonusSystemNode::propagateBonus(Bonus * b)
+void CBonusSystemNode::unpropagateBonus(Bonus * b)
 {
-	whereToPropagate(b)->bonuses.push_back(b);
+	if(b->propagator->shouldBeAttached(this))
+	{
+		bonuses -= b;
+		BONUS_LOG_LINE("#$#" << b->Description() << " #is no longer propagated to# " << nodeName());
+	}
+
+	FOREACH_RED_CHILD(child)
+		child->unpropagateBonus(b);
 }
 
 void CBonusSystemNode::newChildAttached(CBonusSystemNode *child)
@@ -469,6 +480,92 @@ void CBonusSystemNode::deserializationFix()
 	tlog2 << "Deserialization fix called on bare CBSN? Shouldn't be...\n";
 }
 
+void CBonusSystemNode::getRedParents(TNodes &out)
+{
+	FOREACH_PARENT(pname)
+	{
+		if(pname->actsAsBonusSourceOnly())
+		{
+			out.insert(pname);
+		}
+	}
+
+	if(!actsAsBonusSourceOnly())
+	{
+		BOOST_FOREACH(CBonusSystemNode *child, children)
+		{
+			out.insert(child);
+		}
+	}
+}
+
+void CBonusSystemNode::getRedChildren(TNodes &out)
+{
+	FOREACH_PARENT(pname)
+	{
+		if(!pname->actsAsBonusSourceOnly())
+		{
+			out.insert(pname);
+		}
+	}
+
+	if(actsAsBonusSourceOnly())
+	{
+		BOOST_FOREACH(CBonusSystemNode *child, children)
+		{
+			out.insert(child);
+		}
+	}
+}
+
+void CBonusSystemNode::newRedDescendant(CBonusSystemNode *descendant)
+{
+	BOOST_FOREACH(Bonus *b, exportedBonuses)
+		if(b->propagator)
+			descendant->propagateBonus(b);
+
+	FOREACH_RED_PARENT(parent)
+		parent->newRedDescendant(descendant);
+}
+
+void CBonusSystemNode::removedRedDescendant(CBonusSystemNode *descendant)
+{
+	BOOST_FOREACH(Bonus *b, exportedBonuses)
+		if(b->propagator)
+			descendant->unpropagateBonus(b);
+
+	FOREACH_RED_PARENT(parent)
+		parent->removedRedDescendant(descendant);
+}
+
+void CBonusSystemNode::getRedAncestors(TNodes &out)
+{
+	getRedParents(out);
+	FOREACH_RED_PARENT(p)
+		p->getRedAncestors(out);
+}
+
+void CBonusSystemNode::getRedDescendants(TNodes &out)
+{
+	getRedChildren(out);
+	FOREACH_RED_CHILD(c)
+		c->getRedChildren(out);
+}
+
+void CBonusSystemNode::battleTurnPassed()
+{
+	BonusList bonusesCpy = exportedBonuses; //copy, because removing bonuses invalidates iters
+	BOOST_FOREACH(Bonus *b, bonusesCpy)
+	{
+		if(b->duration & Bonus::N_TURNS)
+		{
+			b->turnsRemain--;
+			if(b->turnsRemain <= 0)
+				removeBonus(b);
+		}
+	}
+}
+
 int NBonus::valOf(const CBonusSystemNode *obj, Bonus::BonusType type, int subtype /*= -1*/)
 {
 	if(obj)
@@ -562,7 +659,23 @@ Bonus::~Bonus()
 
 Bonus * Bonus::addLimiter(ILimiter *Limiter)
 {
-	limiter.reset(Limiter);
+	return addLimiter(boost::shared_ptr<ILimiter>(Limiter));
+}
+
+Bonus * Bonus::addLimiter(boost::shared_ptr<ILimiter> Limiter)
+{
+	limiter = Limiter;
+	return this;
+}
+
+Bonus * Bonus::addPropagator(IPropagator *Propagator)
+{
+	return addPropagator(boost::shared_ptr<IPropagator>(Propagator));
+}
+
+Bonus * Bonus::addPropagator(boost::shared_ptr<IPropagator> Propagator)
+{
+	propagator = Propagator;
 	return this;
 }
 
@@ -618,6 +731,27 @@ namespace Selector
 		dummy.subtype = subtype;
 		return sel(&dummy);
 	}
+
+	bool DLL_EXPORT positiveSpellEffects(const Bonus *b)
+	{
+		if(b->source == Bonus::SPELL_EFFECT)
+		{
+			CSpell *sp = VLC->spellh->spells[b->sid];
+			return sp->positiveness == 1;
+		}
+		return false; //not a spell effect
+	}
+}
+
+const CStack * retreiveStackBattle(const CBonusSystemNode *node)
+{
+	switch(node->nodeType)
+	{
+	case CBonusSystemNode::STACK_BATTLE:
+		return static_cast<const CStack*>(node);
+	default:
+		return NULL;
+	}
 }
 
 const CStackInstance * retreiveStackInstance(const CBonusSystemNode *node)
@@ -732,9 +866,35 @@ IPropagator::~IPropagator()
 
 }
 
-CBonusSystemNode * IPropagator::getDestNode(CBonusSystemNode *source)
+// CBonusSystemNode * IPropagator::getDestNode(CBonusSystemNode *source, CBonusSystemNode *redParent, CBonusSystemNode *redChild)
+// {
+// 	tlog1 << "IPropagator::getDestNode called!\n";
+// 	return source;
+// }
+
+bool IPropagator::shouldBeAttached(CBonusSystemNode *dest)
+{
+	return false;
+}
+
+// CBonusSystemNode * CPropagatorNodeType::getDestNode(CBonusSystemNode *source, CBonusSystemNode *redParent, CBonusSystemNode *redChild)
+// {
+// 	return NULL;
+// }
+
+CPropagatorNodeType::CPropagatorNodeType()
+{
+
+}
+
+CPropagatorNodeType::CPropagatorNodeType(ui8 NodeType)
+	: nodeType(NodeType)
 {
-	return source;
+}
+
+bool CPropagatorNodeType::shouldBeAttached(CBonusSystemNode *dest)
+{
+	return nodeType == dest->nodeType;
 }
 
 CreatureNativeTerrainLimiter::CreatureNativeTerrainLimiter(int TerrainType) 
@@ -749,7 +909,8 @@ CreatureNativeTerrainLimiter::CreatureNativeTerrainLimiter()
 bool CreatureNativeTerrainLimiter::limit(const Bonus *b, const CBonusSystemNode &node) const
 {
 	const CCreature *c = retrieveCreature(&node);
-	return !c || VLC->heroh->nativeTerrains[c->faction] != terrainType; //drop bonus for non-creatures or non-native residents
+	return !c || !iswith(c->faction, 0, 9) || VLC->heroh->nativeTerrains[c->faction] != terrainType; //drop bonus for non-creatures or non-native residents
+	//TODO neutral creatues
 }
 
 CreatureFactionLimiter::CreatureFactionLimiter(int Faction)
@@ -799,10 +960,37 @@ RankRangeLimiter::RankRangeLimiter(ui8 Min, ui8 Max)
 {
 }
 
+RankRangeLimiter::RankRangeLimiter()
+{
+	minRank = maxRank = -1;
+}
+
 bool RankRangeLimiter::limit( const Bonus *b, const CBonusSystemNode &node ) const
 {
 	const CStackInstance *csi = retreiveStackInstance(&node);
 	if(csi)
 		return csi->getExpRank() < minRank || csi->getExpRank() > maxRank;
 	return true;
+}
+
+bool StackOwnerLimiter::limit(const Bonus *b, const CBonusSystemNode &node) const 
+{
+	const CStack *s = retreiveStackBattle(&node);
+	if(s)
+		return s->owner != owner;
+
+ 	const CStackInstance *csi = retreiveStackInstance(&node);
+ 	if(csi && csi->armyObj)
+ 		return csi->armyObj->tempOwner != owner;
+ 	return true;
+}
+
+StackOwnerLimiter::StackOwnerLimiter()
+	: owner(-1)
+{
+}
+
+StackOwnerLimiter::StackOwnerLimiter(ui8 Owner)
+	: owner(Owner)
+{
 }

+ 52 - 10
lib/HeroBonus.h

@@ -202,9 +202,7 @@ struct DLL_EXPORT Bonus
 	{
 		NO_LIMIT = 0, 
 		ONLY_DISTANCE_FIGHT=1, ONLY_MELEE_FIGHT, //used to mark bonuses for attack/defense primary skills from spells like Precision (distance only)
-		ONLY_ALLIED_ARMY, ONLY_ENEMY_ARMY,
-		PLAYR_HEROES,
-		GLOBAL //Statue of Legion etc.
+		ONLY_ENEMY_ARMY
 	};
 
 	enum ValueType
@@ -303,6 +301,9 @@ struct DLL_EXPORT Bonus
 	std::string Description() const;
 
 	Bonus *addLimiter(ILimiter *Limiter); //returns this for convenient chain-calls
+	Bonus *addPropagator(IPropagator *Propagator); //returns this for convenient chain-calls
+	Bonus *addLimiter(boost::shared_ptr<ILimiter> Limiter); //returns this for convenient chain-calls
+	Bonus *addPropagator(boost::shared_ptr<IPropagator> Propagator); //returns this for convenient chain-calls
 };
 
 DLL_EXPORT std::ostream & operator<<(std::ostream &out, const Bonus &bonus);
@@ -314,7 +315,6 @@ public:
 	void DLL_EXPORT getBonuses(BonusList &out, const CSelector &selector, const CBonusSystemNode *source = NULL) const;
 	void DLL_EXPORT getBonuses(BonusList &out, const CSelector &selector, const CSelector &limit, const CBonusSystemNode *source = NULL) const;
 	void DLL_EXPORT getModifiersWDescr(TModDescr &out) const;
-	void DLL_EXPORT removeSpells(Bonus::BonusSource sourceType);
 
 	//special find functions
 	DLL_EXPORT Bonus * getFirst(const CSelector &select);
@@ -334,7 +334,26 @@ class DLL_EXPORT IPropagator
 {
 public:
 	virtual ~IPropagator();
-	virtual CBonusSystemNode *getDestNode(CBonusSystemNode *source);
+	virtual bool shouldBeAttached(CBonusSystemNode *dest);
+	//virtual CBonusSystemNode *getDestNode(CBonusSystemNode *source, CBonusSystemNode *redParent, CBonusSystemNode *redChild); //called when red relation between parent-childrem is established / removed
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{}
+};
+
+class DLL_EXPORT CPropagatorNodeType : public IPropagator
+{
+	ui8 nodeType;
+public:
+	CPropagatorNodeType();
+	CPropagatorNodeType(ui8 NodeType);
+	bool shouldBeAttached(CBonusSystemNode *dest);
+	//CBonusSystemNode *getDestNode(CBonusSystemNode *source, CBonusSystemNode *redParent, CBonusSystemNode *redChild) OVERRIDE; 
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & nodeType;
+	}
 };
 	
 class DLL_EXPORT ILimiter
@@ -400,7 +419,11 @@ public:
 
 	const Bonus *getBonus(const CSelector &selector) const;
 	//non-const interface
-	void getParents(TNodes &out);  //retrieves list of parent nodes (nodes to inherit bonuses from), source is the prinary asker
+	void getParents(TNodes &out);  //retrieves list of parent nodes (nodes to inherit bonuses from)
+	void getRedParents(TNodes &out);  //retrieves list of red parent nodes (nodes bonuses propagate from)
+	void getRedAncestors(TNodes &out);
+	void getRedChildren(TNodes &out); 
+	void getRedDescendants(TNodes &out); 
 	Bonus *getBonus(const CSelector &selector);
 
 	void attachTo(CBonusSystemNode *parent);
@@ -411,15 +434,17 @@ public:
 	void newChildAttached(CBonusSystemNode *child);
 	void childDetached(CBonusSystemNode *child);
 	void propagateBonus(Bonus * b);
+	void unpropagateBonus(Bonus * b);
 	//void addNewBonus(const Bonus &b); //b will copied
 	void removeBonus(Bonus *b);
+	void newRedDescendant(CBonusSystemNode *descendant); //propagation needed
+	void removedRedDescendant(CBonusSystemNode *descendant); //de-propagation needed
 
-	TNodesVector &nodesOnWhichWePropagate();
 	bool isIndependentNode() const; //node is independent when it has no parents nor children
-	bool weActAsBonusSourceOnly() const;
+	bool actsAsBonusSourceOnly() const;
 	bool isLimitedOnUs(Bonus *b) const; //if bonus should be removed from list acquired from this node
-	CBonusSystemNode *whereToPropagate(Bonus *b);
 
+	void battleTurnPassed(); //updates count of remaining turns and removed outdated bonuses
 	void popBonuses(const CSelector &s);
 	virtual std::string nodeName() const;
 	void deserializationFix();
@@ -434,7 +459,7 @@ public:
 
 	enum ENodeTypes
 	{
-		UNKNOWN, STACK_INSTANCE, STACK_BATTLE, SPECIALITY, ARTIFACT, CREATURE, ARTIFACT_INSTANCE, HERO
+		UNKNOWN, STACK_INSTANCE, STACK_BATTLE, SPECIALITY, ARTIFACT, CREATURE, ARTIFACT_INSTANCE, HERO, PLAYER, TEAM
 	};
 };
 
@@ -619,11 +644,27 @@ public:
 	}
 };
 
+class DLL_EXPORT StackOwnerLimiter : public ILimiter //applies only to creatures of given alignment
+{
+public:
+	ui8 owner;
+	StackOwnerLimiter();
+	StackOwnerLimiter(ui8 Owner);
+
+	bool limit(const Bonus *b, const CBonusSystemNode &node) const OVERRIDE;
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & owner;
+	}
+};
+
 class DLL_EXPORT RankRangeLimiter : public ILimiter //applies to creatures with min <= Rank <= max
 {
 public:
 	ui8 minRank, maxRank;
 
+	RankRangeLimiter();
 	RankRangeLimiter(ui8 Min, ui8 Max = 255);
 	bool limit(const Bonus *b, const CBonusSystemNode &node) const OVERRIDE;
 
@@ -651,6 +692,7 @@ namespace Selector
 
 	bool DLL_EXPORT matchesType(const CSelector &sel, TBonusType type);
 	bool DLL_EXPORT matchesTypeSubtype(const CSelector &sel, TBonusType type, TBonusSubtype subtype);
+	bool DLL_EXPORT positiveSpellEffects(const Bonus *b);
 }
 
 extern DLL_EXPORT const std::map<std::string, int> bonusNameMap;

+ 23 - 50
lib/NetPacksLib.cpp

@@ -800,15 +800,33 @@ DLL_EXPORT void SetObjectProperty::applyGs( CGameState *gs )
 
 	if(what == ObjProperty::OWNER)
 	{
+		CBonusSystemNode *nodeToMove = NULL;
 		if(obj->ID == TOWNI_TYPE)
 		{
 			CGTownInstance *t = static_cast<CGTownInstance*>(obj);
+			nodeToMove = &t->townAndVis;
 			if(t->tempOwner < PLAYER_LIMIT)
 				gs->getPlayer(t->tempOwner)->towns -= t;
 
 			if(val < PLAYER_LIMIT)
 				gs->getPlayer(val)->towns.push_back(t);
 		}
+		if(CArmedInstance *cai = dynamic_cast<CArmedInstance *>(obj))
+		{
+			if(!nodeToMove)
+				nodeToMove = cai;
+
+			if(obj->tempOwner < PLAYER_LIMIT)
+				nodeToMove->detachFrom(gs->getPlayer(obj->tempOwner));
+			else
+				nodeToMove->detachFrom(&gs->globalEffects);
+
+			if(val < PLAYER_LIMIT)
+				nodeToMove->attachTo(gs->getPlayer(val));
+			else
+				nodeToMove->attachTo(&gs->globalEffects);
+
+		}
 	}
 	
 	obj->setProperty(what,val);
@@ -852,34 +870,7 @@ DLL_EXPORT void BattleNextRound::applyGs( CGameState *gs )
 		if( s->hasBonusOfType(Bonus::FULL_HP_REGENERATION) && s->alive() )
 			s->firstHPleft = s->MaxHealth();
 
-		//remove effects and restore only those with remaining turns in duration
-		BonusList tmpEffects = s->bonuses;
-		s->bonuses.removeSpells(Bonus::SPELL_EFFECT);
-
-		BOOST_FOREACH(Bonus *it, tmpEffects)
-		{
-			it->turnsRemain--;
-			if(it->turnsRemain > 0)
-				s->addNewBonus(it);
-		}
-
-		//the same as above for features
-		BonusList tmpFeatures = s->bonuses;
-		s->bonuses.clear();
-
-		BOOST_FOREACH(Bonus *b, tmpFeatures)
-		{
-			if((b->duration & Bonus::N_TURNS) != 0)
-			{
-				b->turnsRemain--;
-				if(b->turnsRemain > 0)
-					s->addNewBonus(b);
-			}
-			else
-			{
-				s->addNewBonus(b);
-			}
-		}
+		s->battleTurnPassed();
 	}
 }
 
@@ -1048,28 +1039,10 @@ DLL_EXPORT void BattleSpellCast::applyGs( CGameState *gs )
 			CStack *s = gs->curB->getStack(*it);
 			if(s && !vstd::contains(resisted, s->ID)) //if stack exists and it didn't resist
 			{
-				BonusList remainingEff;
-				//WTF?
-				for (BonusList::iterator it = remainingEff.begin(); it != remainingEff.end(); it++)
-				{
-					if (onlyHelpful && VLC->spellh->spells[ (*it)->sid ]->positiveness != 1)
-					{
-						remainingEff.push_back(*it);
-					}
-					
-				}
-				s->bonuses.removeSpells(Bonus::SPELL_EFFECT); //removing all effects
-				s->bonuses = remainingEff; //assigning effects that should remain
-
-				//removing all features from spells
-				BonusList tmpFeatures = s->bonuses;
-				s->bonuses.clear();
-				BOOST_FOREACH(Bonus *b, tmpFeatures)
-				{
-					const CSpell *sp = b->sourceSpell();
-					if(sp && sp->positiveness != 1) //if(b->source != HeroBonus::SPELL_EFFECT || b.positiveness != 1)
-						s->addNewBonus(b);
-				}
+				if(onlyHelpful)
+					s->popBonuses(Selector::positiveSpellEffects);
+				else
+					s->popBonuses(Selector::sourceType(Bonus::SPELL_EFFECT));
 			}
 		}
 	}

+ 5 - 0
lib/RegisterTypes.cpp

@@ -75,12 +75,17 @@ void registerTypes1(Serializer &s)
 	s.template registerType<CGBlackMarket>();
 	s.template registerType<CGUniversity>();
 	//end of objects
+	s.template registerType<IPropagator>();
+	s.template registerType<CPropagatorNodeType>();
+
 	s.template registerType<ILimiter>();
 	s.template registerType<CCreatureTypeLimiter>();
 	s.template registerType<HasAnotherBonusLimiter>();
 	s.template registerType<CreatureNativeTerrainLimiter>();
 	s.template registerType<CreatureFactionLimiter>();
 	s.template registerType<CreatureAlignmentLimiter>();
+	s.template registerType<RankRangeLimiter>();
+	s.template registerType<StackOwnerLimiter>();
 
 	s.template registerType<CBonusSystemNode>();
 	s.template registerType<CArtifact>();

+ 2 - 2
server/CGameHandler.cpp

@@ -2064,8 +2064,8 @@ bool CGameHandler::arrangeStacks( si32 id1, si32 id2, ui8 what, ui8 p1, ui8 p2,
 	}
 	else if(what==3) //split
 	{
-		if ( (s1->tempOwner != player && S1.stacks[p1]->count < s1->getArmy().getStackCount(p1) )
-			|| (s2->tempOwner != player && S2.stacks[p2]->count < s2->getArmy().getStackCount(p2) ) )
+		if ( (s1->tempOwner != player && S1.stacks[p1]->count < s1->getStackCount(p1) )
+			|| (s2->tempOwner != player && S2.stacks[p2]->count < s2->getStackCount(p2) ) )
 		{
 			complain("Can't move troops of another player!");
 			return false;