| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956 | /* * CUnitState.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * * License: GNU General Public License v2.0 or later * Full text of license available in license.txt file, in main folder * */#include "StdInc.h"#include "CUnitState.h"#include "../NetPacks.h"#include "../CCreatureHandler.h"#include "../serializer/JsonDeserializer.h"#include "../serializer/JsonSerializer.h"namespace battle{///CAmmoCAmmo::CAmmo(const battle::Unit * Owner, CSelector totalSelector)	: used(0),	owner(Owner),	totalProxy(Owner, totalSelector){	reset();}CAmmo::CAmmo(const CAmmo & other)	: used(other.used),	owner(other.owner),	totalProxy(other.totalProxy){}CAmmo & CAmmo::operator= (const CAmmo & other){	used = other.used;	totalProxy = other.totalProxy;	return *this;}int32_t CAmmo::available() const{	return total() - used;}bool CAmmo::canUse(int32_t amount) const{	return !isLimited() || (available() - amount >= 0);}bool CAmmo::isLimited() const{	return true;}void CAmmo::reset(){	used = 0;}int32_t CAmmo::total() const{	return totalProxy->totalValue();}void CAmmo::use(int32_t amount){	if(!isLimited())		return;	if(available() - amount < 0)	{		logGlobal->error("Stack ammo overuse. total: %d, used: %d, requested: %d", total(), used, amount);		used += available();	}	else		used += amount;}void CAmmo::serializeJson(JsonSerializeFormat & handler){	handler.serializeInt("used", used, 0);}///CShotsCShots::CShots(const battle::Unit * Owner)	: CAmmo(Owner, Selector::type(Bonus::SHOTS)),	shooter(Owner, Selector::type(Bonus::SHOOTER)){}CShots::CShots(const CShots & other)	: CAmmo(other),	env(other.env),	shooter(other.shooter){}CShots & CShots::operator=(const CShots & other){	CAmmo::operator=(other);	shooter = other.shooter;	return *this;}bool CShots::isLimited() const{	return !env->unitHasAmmoCart(owner) || !shooter.getHasBonus();}void CShots::setEnv(const IUnitEnvironment * env_){	env = env_;}int32_t CShots::total() const{	if(shooter.getHasBonus())		return CAmmo::total();	else		return 0;}///CCastsCCasts::CCasts(const battle::Unit * Owner):	CAmmo(Owner, Selector::type(Bonus::CASTS)){}CCasts::CCasts(const CCasts & other)	: CAmmo(other){}CCasts & CCasts::operator=(const CCasts & other){	CAmmo::operator=(other);	return *this;}///CRetaliationsCRetaliations::CRetaliations(const battle::Unit * Owner)	: CAmmo(Owner, Selector::type(Bonus::ADDITIONAL_RETALIATION)),	totalCache(0),	noRetaliation(Owner, Selector::type(Bonus::SIEGE_WEAPON).Or(Selector::type(Bonus::HYPNOTIZED)).Or(Selector::type(Bonus::NO_RETALIATION))),	unlimited(Owner, Selector::type(Bonus::UNLIMITED_RETALIATIONS)){}CRetaliations::CRetaliations(const CRetaliations & other)	: CAmmo(other),	totalCache(other.totalCache),	noRetaliation(other.noRetaliation),	unlimited(other.unlimited){}CRetaliations & CRetaliations::operator=(const CRetaliations & other){	CAmmo::operator=(other);	totalCache = other.totalCache;	noRetaliation = other.noRetaliation;	unlimited = other.unlimited;	return *this;}bool CRetaliations::isLimited() const{	return !unlimited.getHasBonus() || noRetaliation.getHasBonus();}int32_t CRetaliations::total() const{	if(noRetaliation.getHasBonus())		return 0;	//after dispell bonus should remain during current round	int32_t val = 1 + totalProxy->totalValue();	vstd::amax(totalCache, val);	return totalCache;}void CRetaliations::reset(){	CAmmo::reset();	totalCache = 0;}void CRetaliations::serializeJson(JsonSerializeFormat & handler){	CAmmo::serializeJson(handler);	//we may be serialized in the middle of turn	handler.serializeInt("totalCache", totalCache, 0);}///CHealthCHealth::CHealth(const battle::Unit * Owner):	owner(Owner){	reset();}CHealth::CHealth(const CHealth & other):	owner(other.owner),	firstHPleft(other.firstHPleft),	fullUnits(other.fullUnits),	resurrected(other.resurrected){}CHealth & CHealth::operator=(const CHealth & other){	//do not change owner	firstHPleft = other.firstHPleft;	fullUnits = other.fullUnits;	resurrected = other.resurrected;	return *this;}void CHealth::init(){	reset();	fullUnits = owner->unitBaseAmount() > 1 ? owner->unitBaseAmount() - 1 : 0;	firstHPleft = owner->unitBaseAmount() > 0 ? owner->MaxHealth() : 0;}void CHealth::addResurrected(int32_t amount){	resurrected += amount;	vstd::amax(resurrected, 0);}int64_t CHealth::available() const{	return static_cast<int64_t>(firstHPleft) + owner->MaxHealth() * fullUnits;}int64_t CHealth::total() const{	return static_cast<int64_t>(owner->MaxHealth()) * owner->unitBaseAmount();}void CHealth::damage(int64_t & amount){	const int32_t oldCount = getCount();	const bool withKills = amount >= firstHPleft;	if(withKills)	{		int64_t totalHealth = available();		if(amount > totalHealth)			amount = totalHealth;		totalHealth -= amount;		if(totalHealth <= 0)		{			fullUnits = 0;			firstHPleft = 0;		}		else		{			setFromTotal(totalHealth);		}	}	else	{		firstHPleft -= static_cast<int32_t>(amount);	}	addResurrected(getCount() - oldCount);}void CHealth::heal(int64_t & amount, EHealLevel level, EHealPower power){	const int32_t unitHealth = owner->MaxHealth();	const int32_t oldCount = getCount();	int64_t maxHeal = std::numeric_limits<int64_t>::max();	switch(level)	{	case EHealLevel::HEAL:		maxHeal = std::max(0, unitHealth - firstHPleft);		break;	case EHealLevel::RESURRECT:		maxHeal = total() - available();		break;	default:		assert(level == EHealLevel::OVERHEAL);		break;	}	vstd::amax(maxHeal, 0);	vstd::abetween(amount, 0, maxHeal);	if(amount == 0)		return;	int64_t availableHealth = available();	availableHealth	+= amount;	setFromTotal(availableHealth);	if(power == EHealPower::ONE_BATTLE)		addResurrected(getCount() - oldCount);	else		assert(power == EHealPower::PERMANENT);}void CHealth::setFromTotal(const int64_t totalHealth){	const int32_t unitHealth = owner->MaxHealth();	firstHPleft = totalHealth % unitHealth;	fullUnits = static_cast<int32_t>(totalHealth / unitHealth);	if(firstHPleft == 0 && fullUnits >= 1)	{		firstHPleft = unitHealth;		fullUnits -= 1;	}}void CHealth::reset(){	fullUnits = 0;	firstHPleft = 0;	resurrected = 0;}int32_t CHealth::getCount() const{	return fullUnits + (firstHPleft > 0 ? 1 : 0);}int32_t CHealth::getFirstHPleft() const{	return firstHPleft;}int32_t CHealth::getResurrected() const{	return resurrected;}void CHealth::takeResurrected(){	if(resurrected != 0)	{		int64_t totalHealth = available();		totalHealth -= resurrected * owner->MaxHealth();		vstd::amax(totalHealth, 0);		setFromTotal(totalHealth);		resurrected = 0;	}}void CHealth::serializeJson(JsonSerializeFormat & handler){	handler.serializeInt("firstHPleft", firstHPleft, 0);	handler.serializeInt("fullUnits", fullUnits, 0);	handler.serializeInt("resurrected", resurrected, 0);}///CUnitStateCUnitState::CUnitState()	: env(nullptr),	cloned(false),	defending(false),	defendingAnim(false),	drainedMana(false),	fear(false),	hadMorale(false),	ghost(false),	ghostPending(false),	movedThisRound(false),	summoned(false),	waiting(false),	waitedThisTurn(false),	casts(this),	counterAttacks(this),	health(this),	shots(this),	totalAttacks(this, Selector::type(Bonus::ADDITIONAL_ATTACK), 1),	minDamage(this, Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 0).Or(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 1)), 0),	maxDamage(this, Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 0).Or(Selector::typeSubtype(Bonus::CREATURE_DAMAGE, 2)), 0),	attack(this, Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::ATTACK), 0),	defence(this, Selector::typeSubtype(Bonus::PRIMARY_SKILL, PrimarySkill::DEFENSE), 0),	inFrenzy(this, Selector::type(Bonus::IN_FRENZY)),	cloneLifetimeMarker(this, Selector::type(Bonus::NONE).And(Selector::source(Bonus::SPELL_EFFECT, SpellID::CLONE))),	cloneID(-1),	position(){}CUnitState & CUnitState::operator=(const CUnitState & other){	//do not change unit and bonus info	cloned = other.cloned;	defending = other.defending;	defendingAnim = other.defendingAnim;	drainedMana = other.drainedMana;	fear = other.fear;	hadMorale = other.hadMorale;	ghost = other.ghost;	ghostPending = other.ghostPending;	movedThisRound = other.movedThisRound;	summoned = other.summoned;	waiting = other.waiting;	waitedThisTurn = other.waitedThisTurn;	casts = other.casts;	counterAttacks = other.counterAttacks;	health = other.health;	shots = other.shots;	totalAttacks = other.totalAttacks;	minDamage = other.minDamage;	maxDamage = other.maxDamage;	attack = other.attack;	defence = other.defence;	inFrenzy = other.inFrenzy;	cloneLifetimeMarker = other.cloneLifetimeMarker;	cloneID = other.cloneID;	position = other.position;	return *this;}int32_t CUnitState::creatureIndex() const{	return static_cast<int32_t>(creatureId().toEnum());}CreatureID CUnitState::creatureId() const{	return unitType()->idNumber;}int32_t CUnitState::creatureLevel() const{	return static_cast<int32_t>(unitType()->level);}bool CUnitState::doubleWide() const{	return unitType()->doubleWide;}int32_t CUnitState::creatureCost() const{	return unitType()->cost[Res::GOLD];}int32_t CUnitState::creatureIconIndex() const{	return unitType()->iconIndex;}int32_t CUnitState::getCasterUnitId() const{	return static_cast<int32_t>(unitId());}ui8 CUnitState::getSpellSchoolLevel(const spells::Spell * spell, int * outSelectedSchool) const{	int skill = valOfBonuses(Selector::typeSubtype(Bonus::SPELLCASTER, spell->getIndex()));	vstd::abetween(skill, 0, 3);	return skill;}int64_t CUnitState::getSpellBonus(const spells::Spell * spell, int64_t base, const Unit * affectedStack) const{	//does not have sorcery-like bonuses (yet?)	return base;}int64_t CUnitState::getSpecificSpellBonus(const spells::Spell * spell, int64_t base) const{	return base;}int CUnitState::getEffectLevel(const spells::Spell * spell) const{	return getSpellSchoolLevel(spell);}int CUnitState::getEffectPower(const spells::Spell * spell) const{	return valOfBonuses(Bonus::CREATURE_SPELL_POWER) * getCount() / 100;}int CUnitState::getEnchantPower(const spells::Spell * spell) const{	int res = valOfBonuses(Bonus::CREATURE_ENCHANT_POWER);	if(res <= 0)		res = 3;//default for creatures	return res;}int64_t CUnitState::getEffectValue(const spells::Spell * spell) const{	return static_cast<int64_t>(getCount()) * valOfBonuses(Bonus::SPECIFIC_SPELL_POWER, spell->getIndex());}const PlayerColor CUnitState::getOwner() const{	return env->unitEffectiveOwner(this);}void CUnitState::getCasterName(MetaString & text) const{	//always plural name in case of spell cast.	addNameReplacement(text, true);}void CUnitState::getCastDescription(const spells::Spell * spell, const std::vector<const Unit *> & attacked, MetaString & text) const{	text.addTxt(MetaString::GENERAL_TXT, 565);//The %s casts %s	//todo: use text 566 for single creature	getCasterName(text);	text.addReplacement(MetaString::SPELL_NAME, spell->getIndex());}bool CUnitState::ableToRetaliate() const{	return alive()		&& counterAttacks.canUse();}bool CUnitState::alive() const{	return health.getCount() > 0;}bool CUnitState::isGhost() const{	return ghost;}bool CUnitState::isValidTarget(bool allowDead) const{	return (alive() || (allowDead && isDead())) && getPosition().isValid() && !isTurret();}bool CUnitState::isClone() const{	return cloned;}bool CUnitState::hasClone() const{	return cloneID > 0;}bool CUnitState::canCast() const{	return casts.canUse(1);//do not check specific cast abilities here}bool CUnitState::isCaster() const{	return casts.total() > 0;//do not check specific cast abilities here}bool CUnitState::canShoot() const{	return shots.canUse(1);}bool CUnitState::isShooter() const{	return shots.total() > 0;}int32_t CUnitState::getKilled() const{	int32_t res = unitBaseAmount() - health.getCount() + health.getResurrected();	vstd::amax(res, 0);	return res;}int32_t CUnitState::getCount() const{	return health.getCount();}int32_t CUnitState::getFirstHPleft() const{	return health.getFirstHPleft();}int64_t CUnitState::getAvailableHealth() const{	return health.available();}int64_t CUnitState::getTotalHealth() const{	return health.total();}BattleHex CUnitState::getPosition() const{	return position;}void CUnitState::setPosition(BattleHex hex){	position = hex;}int32_t CUnitState::getInitiative(int turn) const{	return valOfBonuses(Selector::type(Bonus::STACKS_SPEED).And(Selector::turns(turn)));}bool CUnitState::canMove(int turn) const{	return alive() && !hasBonus(Selector::type(Bonus::NOT_ACTIVE).And(Selector::turns(turn))); //eg. Ammo Cart or blinded creature}bool CUnitState::defended(int turn) const{	return !turn && defending;}bool CUnitState::moved(int turn) const{	if(!turn && !waiting)		return movedThisRound;	else		return false;}bool CUnitState::willMove(int turn) const{	return (turn ? true : !defending)		   && !moved(turn)		   && canMove(turn);}bool CUnitState::waited(int turn) const{	if(!turn)		return waiting;	else		return false;}int CUnitState::battleQueuePhase(int turn) const{	if(turn <= 0 && waited()) //consider waiting state only for ongoing round	{		if(hadMorale)			return 2;		else			return 3;	}	else if(creatureIndex() == CreatureID::CATAPULT || isTurret()) //catapult and turrets are first	{		return 0;	}	else	{		return 1;	}}int CUnitState::getTotalAttacks(bool ranged) const{	return ranged ? totalAttacks.getRangedValue() : totalAttacks.getMeleeValue();}int CUnitState::getMinDamage(bool ranged) const{	return ranged ? minDamage.getRangedValue() : minDamage.getMeleeValue();}int CUnitState::getMaxDamage(bool ranged) const{	return ranged ? maxDamage.getRangedValue() : maxDamage.getMeleeValue();}int CUnitState::getAttack(bool ranged) const{	int ret = ranged ? attack.getRangedValue() : attack.getMeleeValue();	if(!inFrenzy->empty())	{		double frenzyPower = (double)inFrenzy->totalValue() / 100;		frenzyPower *= (double) (ranged ? defence.getRangedValue() : defence.getMeleeValue());		ret += static_cast<int>(frenzyPower);	}	vstd::amax(ret, 0);	return ret;}int CUnitState::getDefence(bool ranged) const{	if(!inFrenzy->empty())	{		return 0;	}	else	{		int ret = ranged ? defence.getRangedValue() : defence.getMeleeValue();		vstd::amax(ret, 0);		return ret;	}}std::shared_ptr<Unit> CUnitState::acquire() const{	auto ret = std::make_shared<CUnitStateDetached>(this, this);	ret->localInit(env);	*ret = *this;	return ret;}std::shared_ptr<CUnitState> CUnitState::acquireState() const{	auto ret = std::make_shared<CUnitStateDetached>(this, this);	ret->localInit(env);	*ret = *this;	return ret;}void CUnitState::serializeJson(JsonSerializeFormat & handler){	if(!handler.saving)		reset();	handler.serializeBool("cloned", cloned);	handler.serializeBool("defending", defending);	handler.serializeBool("defendingAnim", defendingAnim);	handler.serializeBool("drainedMana", drainedMana);	handler.serializeBool("fear", fear);	handler.serializeBool("hadMorale", hadMorale);	handler.serializeBool("ghost", ghost);	handler.serializeBool("ghostPending", ghostPending);	handler.serializeBool("moved", movedThisRound);	handler.serializeBool("summoned", summoned);	handler.serializeBool("waiting", waiting);	handler.serializeBool("waitedThisTurn", waitedThisTurn);	handler.serializeStruct("casts", casts);	handler.serializeStruct("counterAttacks", counterAttacks);	handler.serializeStruct("health", health);	handler.serializeStruct("shots", shots);	handler.serializeInt("cloneID", cloneID);	handler.serializeInt("position", position);}void CUnitState::localInit(const IUnitEnvironment * env_){	env = env_;	shots.setEnv(env);	reset();	health.init();}void CUnitState::reset(){	cloned = false;	defending = false;	defendingAnim = false;	drainedMana = false;	fear = false;	hadMorale = false;	ghost = false;	ghostPending = false;	movedThisRound = false;	summoned = false;	waiting = false;	waitedThisTurn = false;	casts.reset();	counterAttacks.reset();	health.reset();	shots.reset();	cloneID = -1;	position = BattleHex::INVALID;}void CUnitState::save(JsonNode & data){	//TODO: use instance resolver	data.clear();	JsonSerializer ser(nullptr, data);	ser.serializeStruct("state", *this);}void CUnitState::load(const JsonNode & data){	//TODO: use instance resolver	reset();    JsonDeserializer deser(nullptr, data);    deser.serializeStruct("state", *this);}void CUnitState::damage(int64_t & amount){	if(cloned)	{		// block ability should not kill clone (0 damage)		if(amount > 0)		{			amount = 1;//TODO: what should be actual damage against clone?			health.reset();		}	}	else	{		health.damage(amount);	}	if(health.available() <= 0 && (cloned || summoned))		ghostPending = true;}void CUnitState::heal(int64_t & amount, EHealLevel level, EHealPower power){	if(level == EHealLevel::HEAL && power == EHealPower::ONE_BATTLE)		logGlobal->error("Heal for one battle does not make sense");	else if(cloned)		logGlobal->error("Attempt to heal clone");	else		health.heal(amount, level, power);}void CUnitState::afterAttack(bool ranged, bool counter){	if(counter)		counterAttacks.use();	if(ranged)		shots.use();}void CUnitState::afterNewRound(){	defending = false;	waiting = false;	waitedThisTurn = false;	movedThisRound = false;	hadMorale = false;	fear = false;	drainedMana = false;	counterAttacks.reset();	if(alive() && isClone())	{		if(!cloneLifetimeMarker.getHasBonus())			makeGhost();	}}void CUnitState::afterGetsTurn(){	//if moving second time this round it must be high morale bonus	if(movedThisRound)		hadMorale = true;}void CUnitState::makeGhost(){	health.reset();	ghostPending = true;}void CUnitState::onRemoved(){	health.reset();	ghostPending = false;	ghost = true;}CUnitStateDetached::CUnitStateDetached(const IUnitInfo * unit_, const IBonusBearer * bonus_)	: CUnitState(),	unit(unit_),	bonus(bonus_){}TConstBonusListPtr CUnitStateDetached::getAllBonuses(const CSelector & selector, const CSelector & limit, const CBonusSystemNode * root, const std::string & cachingStr) const{	return bonus->getAllBonuses(selector, limit, root, cachingStr);}int64_t CUnitStateDetached::getTreeVersion() const{	return bonus->getTreeVersion();}CUnitStateDetached & CUnitStateDetached::operator=(const CUnitState & other){	CUnitState::operator=(other);	return *this;}uint32_t CUnitStateDetached::unitId() const{	return unit->unitId();}ui8 CUnitStateDetached::unitSide() const{	return unit->unitSide();}const CCreature * CUnitStateDetached::unitType() const{	return unit->unitType();}PlayerColor CUnitStateDetached::unitOwner() const{	return unit->unitOwner();}SlotID CUnitStateDetached::unitSlot() const{	return unit->unitSlot();}int32_t CUnitStateDetached::unitBaseAmount() const{	return unit->unitBaseAmount();}void CUnitStateDetached::spendMana(const spells::PacketSender * server, const int spellCost) const{	if(spellCost != 1)		logGlobal->warn("Unexpected spell cost %d for creature", spellCost);	//this is evil, but	//use of netpacks in detached state is an error	//non const API is more evil for hero	const_cast<CUnitStateDetached *>(this)->casts.use(spellCost);}}
 |