mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	Extract adventure spell mechanics
This commit is contained in:
		| @@ -328,6 +328,11 @@ | ||||
| 			"icon":  "zvs/Lib1.res/E_MIND" | ||||
| 		} | ||||
| 	}, | ||||
| 	 | ||||
| 	"NONE": | ||||
| 	{ | ||||
| 		"hidden": true | ||||
| 	}, | ||||
|  | ||||
| 	"NO_DISTANCE_PENALTY": | ||||
| 	{ | ||||
|   | ||||
| @@ -127,7 +127,7 @@ CBonusTypeHandler::~CBonusTypeHandler() | ||||
| } | ||||
|  | ||||
| std::string CBonusTypeHandler::bonusToString(const Bonus *bonus, const IBonusBearer *bearer, bool description) const | ||||
| { | ||||
| {	 | ||||
| 	auto getValue = [=](const std::string &name) -> std::string | ||||
| 	{ | ||||
| 		if (name == "val") | ||||
| @@ -156,6 +156,8 @@ std::string CBonusTypeHandler::bonusToString(const Bonus *bonus, const IBonusBea | ||||
| 	}; | ||||
| 	 | ||||
| 	const CBonusType& bt = bonusTypes[bonus->type];	 | ||||
| 	if(bt.hidden) | ||||
| 		return ""; | ||||
| 	const MacroString& macro = description ? bt.description : bt.name; | ||||
| 	 | ||||
| 	return macro.build(getValue);	 | ||||
|   | ||||
| @@ -113,6 +113,12 @@ void CSpell::afterCast(BattleInfo * battle, const BattleSpellCast * packet) cons | ||||
| 	mechanics->afterCast(battle, packet); | ||||
| } | ||||
|  | ||||
| bool CSpell::adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const | ||||
| { | ||||
| 	assert(env); | ||||
| 		 | ||||
| 	return mechanics->adventureCast(env, parameters); | ||||
| } | ||||
|  | ||||
| void CSpell::battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const | ||||
| { | ||||
|   | ||||
| @@ -18,6 +18,8 @@ | ||||
|  * | ||||
|  */ | ||||
|  | ||||
| class CGObjectInstance; | ||||
|  | ||||
| class CSpell; | ||||
| class ISpellMechanics; | ||||
|  | ||||
| @@ -32,7 +34,9 @@ class BattleInfo; | ||||
| struct CPackForClient; | ||||
| struct BattleSpellCast; | ||||
|  | ||||
| class CGameInfoCallback; | ||||
| class CRandomGenerator; | ||||
| class CMap; | ||||
|  | ||||
| struct SpellSchoolInfo | ||||
| { | ||||
| @@ -53,6 +57,11 @@ public: | ||||
| 	 | ||||
| 	virtual CRandomGenerator & getRandomGenerator() const = 0; | ||||
| 	virtual void complain(const std::string & problem) const = 0; | ||||
| 	 | ||||
| 	virtual const CMap * getMap() const = 0; | ||||
| 	virtual const CGameInfoCallback * getCb() const = 0;	 | ||||
| 	 | ||||
| 	virtual bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, PlayerColor asker = PlayerColor::NEUTRAL) const =0;	//TODO: remove | ||||
| }; | ||||
|  | ||||
| ///helper struct | ||||
| @@ -73,6 +82,12 @@ public: | ||||
| 	const BattleInfo * cb;		 | ||||
| }; | ||||
|  | ||||
| struct DLL_LINKAGE AdventureSpellCastParameters | ||||
| { | ||||
| 	const CGHeroInstance * caster; | ||||
| 	int3 pos;	 | ||||
| }; | ||||
|  | ||||
| enum class VerticalPosition : ui8{TOP, CENTER, BOTTOM}; | ||||
|  | ||||
| class DLL_LINKAGE CSpell | ||||
| @@ -314,7 +329,7 @@ public: | ||||
| 	///Server logic. Has write access to GameState via packets. | ||||
| 	///May be executed on client side by (future) non-cheat-proof scripts. | ||||
| 	 | ||||
| 	//void adventureCast() const;  | ||||
| 	bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const;  | ||||
| 	void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const; 	 | ||||
| 		 | ||||
| public:	 | ||||
|   | ||||
| @@ -18,6 +18,10 @@ | ||||
|  | ||||
| #include "NetPacks.h" | ||||
|  | ||||
| #include "mapping/CMap.h" | ||||
| #include "CGameInfoCallback.h" | ||||
| #include "CGameState.h" | ||||
|  | ||||
| namespace SRSLPraserHelpers | ||||
| { | ||||
| 	static int XYToHex(int x, int y) | ||||
| @@ -139,7 +143,7 @@ public: | ||||
| 	 | ||||
| 	ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const override; | ||||
| 	 | ||||
| 	//bool adventureCast(const SpellCastContext & context) const override;  | ||||
| 	bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override final;  | ||||
| 	void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const override; | ||||
| 	 | ||||
| 	void afterCast(BattleInfo * battle, const BattleSpellCast * packet) const override; | ||||
| @@ -149,8 +153,58 @@ protected: | ||||
| 	 | ||||
| 	virtual int calculateDuration(const CGHeroInstance * caster, int usedSpellPower) const; | ||||
| 	 | ||||
| 	///actual adventure cast implementation | ||||
| 	virtual bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const; | ||||
| }; | ||||
|  | ||||
| ///ADVENTURE SPELLS | ||||
|  | ||||
| //todo: make configyrable | ||||
| class AdventureBonusingMechanics: public DefaultSpellMechanics  | ||||
| { | ||||
| public:	 | ||||
| 	AdventureBonusingMechanics(CSpell * s, Bonus::BonusType _bonusTypeID): DefaultSpellMechanics(s), bonusTypeID(_bonusTypeID){};	 | ||||
| protected: | ||||
| 	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;	 | ||||
| private: | ||||
| 	Bonus::BonusType bonusTypeID; | ||||
| }; | ||||
|  | ||||
| class SummonBoatMechanics: public DefaultSpellMechanics  | ||||
| { | ||||
| public: | ||||
| 	SummonBoatMechanics(CSpell * s): DefaultSpellMechanics(s){}; | ||||
| protected: | ||||
| 	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;	 | ||||
| }; | ||||
|  | ||||
| class ScuttleBoatMechanics: public DefaultSpellMechanics  | ||||
| { | ||||
| public: | ||||
| 	ScuttleBoatMechanics(CSpell * s): DefaultSpellMechanics(s){}; | ||||
| protected: | ||||
| 	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;	 | ||||
| }; | ||||
|  | ||||
| class DimensionDoorMechanics: public DefaultSpellMechanics  | ||||
| { | ||||
| public:	 | ||||
| 	DimensionDoorMechanics(CSpell * s): DefaultSpellMechanics(s){};	 | ||||
| protected: | ||||
| 	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;	 | ||||
| }; | ||||
|  | ||||
| class TownPortalMechanics: public DefaultSpellMechanics  | ||||
| { | ||||
| public:	 | ||||
| 	TownPortalMechanics(CSpell * s): DefaultSpellMechanics(s){};	 | ||||
| protected: | ||||
| 	bool applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const override;	 | ||||
| }; | ||||
|  | ||||
|  | ||||
| ///BATTLE SPELLS | ||||
|  | ||||
| class AcidBreathDamageMechnics: public DefaultSpellMechanics | ||||
| { | ||||
| public: | ||||
| @@ -323,7 +377,23 @@ ISpellMechanics * ISpellMechanics::createMechanics(CSpell * s) | ||||
| 	case SpellID::SUMMON_AIR_ELEMENTAL: | ||||
| 		return new SummonMechanics(s); | ||||
| 	case SpellID::TELEPORT: | ||||
| 		return new TeleportMechanics(s); | ||||
| 		return new TeleportMechanics(s);	 | ||||
| 	case SpellID::SUMMON_BOAT: | ||||
| 		return new SummonBoatMechanics(s); | ||||
| 	case SpellID::SCUTTLE_BOAT:		 | ||||
| 		return new ScuttleBoatMechanics(s);		 | ||||
| 	case SpellID::DIMENSION_DOOR: | ||||
| 		return new DimensionDoorMechanics(s); | ||||
| 	case SpellID::FLY: | ||||
| 		return new AdventureBonusingMechanics(s, Bonus::FLYING_MOVEMENT); | ||||
| 	case SpellID::WATER_WALK: | ||||
| 		return new AdventureBonusingMechanics(s, Bonus::WATER_WALKING); | ||||
| 	case SpellID::TOWN_PORTAL: | ||||
| 	 | ||||
| 	case SpellID::VISIONS: | ||||
| 	case SpellID::VIEW_EARTH: | ||||
| 	case SpellID::DISGUISE: | ||||
| 	case SpellID::VIEW_AIR:	 | ||||
| 	default:		 | ||||
| 		if(s->isRisingSpell()) | ||||
| 			return new SpecialRisingSpellMechanics(s); | ||||
| @@ -362,6 +432,55 @@ void DefaultSpellMechanics::afterCast(BattleInfo * battle, const BattleSpellCast | ||||
| 	}	 | ||||
| } | ||||
|  | ||||
| bool DefaultSpellMechanics::adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const | ||||
| { | ||||
| 	if(!owner->isAdventureSpell()) | ||||
| 	{ | ||||
| 		env->complain("Attempt to cast non adventure spell in adventure mode"); | ||||
| 		return false; | ||||
| 	} | ||||
| 	 | ||||
| 	const CGHeroInstance * caster = parameters.caster; | ||||
| 	const int cost = caster->getSpellCost(owner); | ||||
| 	 | ||||
| 	if(!caster->canCastThisSpell(owner)) | ||||
| 	{ | ||||
| 		env->complain("Hero cannot cast this spell!"); | ||||
| 		return false;		 | ||||
| 	} | ||||
|  | ||||
| 	if(caster->mana < cost) | ||||
| 	{ | ||||
| 		env->complain("Hero doesn't have enough spell points to cast this spell!"); | ||||
| 		return false;		 | ||||
| 	} | ||||
|  | ||||
| 	{ | ||||
| 		AdvmapSpellCast asc; | ||||
| 		asc.caster = caster; | ||||
| 		asc.spellID = owner->id; | ||||
| 		env->sendAndApply(&asc);		 | ||||
| 	} | ||||
| 	 | ||||
| 	if(applyAdventureEffects(env, parameters)) | ||||
| 	{ | ||||
| 		SetMana sm; | ||||
| 		sm.hid = caster->id; | ||||
| 		sm.absolute = false; | ||||
| 		sm.val = -cost; | ||||
| 		env->sendAndApply(&sm); | ||||
| 		return true; | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| bool DefaultSpellMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const | ||||
| { | ||||
| 	//There is no generic algorithm of adventure cast | ||||
| 	env->complain("Unimplemented adventure spell"); | ||||
| 	return false;		 | ||||
| } | ||||
|  | ||||
|  | ||||
| void DefaultSpellMechanics::battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const | ||||
| { | ||||
| @@ -847,6 +966,245 @@ ESpellCastProblem::ESpellCastProblem DefaultSpellMechanics::isImmuneByStack(cons | ||||
| 	return owner->isImmuneBy(obj); | ||||
| } | ||||
|  | ||||
| ///ADVENTURE SPELLS | ||||
|  | ||||
| ///AdventureBonusingMechanics | ||||
| bool AdventureBonusingMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const | ||||
| { | ||||
| 	const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);	 | ||||
| 	const int subtype = schoolLevel >= 2 ? 1 : 2; //adv or expert | ||||
|  | ||||
| 	GiveBonus gb; | ||||
| 	gb.id = parameters.caster->id.getNum(); | ||||
| 	gb.bonus = Bonus(Bonus::ONE_DAY, bonusTypeID, Bonus::SPELL_EFFECT, 0, owner->id, subtype); | ||||
| 	env->sendAndApply(&gb);	 | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| ///SummonBoatMechanics | ||||
| bool SummonBoatMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const | ||||
| { | ||||
| 	const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner);	 | ||||
| 	//check if spell works at all | ||||
| 	if(env->getRandomGenerator().nextInt(99) >= owner->getPower(schoolLevel)) //power is % chance of success | ||||
| 	{ | ||||
| 		InfoWindow iw; | ||||
| 		iw.player = parameters.caster->tempOwner; | ||||
| 		iw.text.addTxt(MetaString::GENERAL_TXT, 336); //%s tried to summon a boat, but failed. | ||||
| 		iw.text.addReplacement(parameters.caster->name); | ||||
| 		env->sendAndApply(&iw); | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	//try to find unoccupied boat to summon | ||||
| 	const CGBoat * nearest = nullptr; | ||||
| 	double dist = 0; | ||||
| 	int3 summonPos = parameters.caster->bestLocation(); | ||||
| 	if(summonPos.x < 0) | ||||
| 	{ | ||||
| 		env->complain("There is no water tile available!"); | ||||
| 		return false; | ||||
| 	}	 | ||||
| 	 | ||||
| 	for(const CGObjectInstance * obj : env->getMap()->objects) | ||||
| 	{ | ||||
| 		if(obj && obj->ID == Obj::BOAT) | ||||
| 		{ | ||||
| 			const CGBoat *b = static_cast<const CGBoat*>(obj); | ||||
| 			if(b->hero)  | ||||
| 				continue; //we're looking for unoccupied boat | ||||
|  | ||||
| 			double nDist = b->pos.dist2d(parameters.caster->getPosition()); | ||||
| 			if(!nearest || nDist < dist) //it's first boat or closer than previous | ||||
| 			{ | ||||
| 				nearest = b; | ||||
| 				dist = nDist; | ||||
| 			} | ||||
| 		}						 | ||||
| 	} | ||||
|  | ||||
| 	if(nullptr != nearest) //we found boat to summon | ||||
| 	{ | ||||
| 		ChangeObjPos cop; | ||||
| 		cop.objid = nearest->id; | ||||
| 		cop.nPos = summonPos + int3(1,0,0);; | ||||
| 		cop.flags = 1; | ||||
| 		env->sendAndApply(&cop); | ||||
| 	} | ||||
| 	else if(schoolLevel < 2) //none or basic level -> cannot create boat :( | ||||
| 	{ | ||||
| 		InfoWindow iw; | ||||
| 		iw.player = parameters.caster->tempOwner; | ||||
| 		iw.text.addTxt(MetaString::GENERAL_TXT, 335); //There are no boats to summon. | ||||
| 		env->sendAndApply(&iw); | ||||
| 	} | ||||
| 	else //create boat | ||||
| 	{ | ||||
| 		NewObject no; | ||||
| 		no.ID = Obj::BOAT; | ||||
| 		no.subID = parameters.caster->getBoatType(); | ||||
| 		no.pos = summonPos + int3(1,0,0);; | ||||
| 		env->sendAndApply(&no); | ||||
| 	}	 | ||||
| 	return true;	 | ||||
| } | ||||
|  | ||||
| ///ScuttleBoatMechanics | ||||
| bool ScuttleBoatMechanics::applyAdventureEffects(const SpellCastEnvironment* env, AdventureSpellCastParameters& parameters) const | ||||
| { | ||||
| 	const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner); | ||||
| 	//check if spell works at all | ||||
| 	if(env->getRandomGenerator().nextInt(99) >= owner->getPower(schoolLevel)) //power is % chance of success | ||||
| 	{ | ||||
| 		InfoWindow iw; | ||||
| 		iw.player = parameters.caster->tempOwner; | ||||
| 		iw.text.addTxt(MetaString::GENERAL_TXT, 337); //%s tried to scuttle the boat, but failed | ||||
| 		iw.text.addReplacement(parameters.caster->name); | ||||
| 		env->sendAndApply(&iw); | ||||
| 		return true; | ||||
| 	} | ||||
| 		 | ||||
| 	if(!env->getMap()->isInTheMap(parameters.pos)) | ||||
| 	{ | ||||
| 		env->complain("Invalid dst tile for scuttle!"); | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	//TODO: test range, visibility | ||||
| 	const TerrainTile *t = &env->getMap()->getTile(parameters.pos); | ||||
| 	if(!t->visitableObjects.size() || t->visitableObjects.back()->ID != Obj::BOAT) | ||||
| 	{ | ||||
| 		env->complain("There is no boat to scuttle!"); | ||||
| 		return false; | ||||
| 	} | ||||
| 		 | ||||
|  | ||||
| 	RemoveObject ro; | ||||
| 	ro.id = t->visitableObjects.back()->id; | ||||
| 	env->sendAndApply(&ro); | ||||
| 	return true;	 | ||||
| } | ||||
|  | ||||
| ///DimensionDoorMechanics | ||||
| bool DimensionDoorMechanics::applyAdventureEffects(const SpellCastEnvironment* env, AdventureSpellCastParameters& parameters) const | ||||
| { | ||||
| 	if(!env->getMap()->isInTheMap(parameters.pos)) | ||||
| 	{ | ||||
| 		env->complain("Destination is out of map!"); | ||||
| 		return false; | ||||
| 	}	 | ||||
| 	 | ||||
| 	const TerrainTile * dest = env->getCb()->getTile(parameters.pos); | ||||
| 	const TerrainTile * curr = env->getCb()->getTile(parameters.caster->getSightCenter()); | ||||
|  | ||||
| 	if(nullptr == dest) | ||||
| 	{ | ||||
| 		env->complain("Destination tile doesn't exist!"); | ||||
| 		return false; | ||||
| 	} | ||||
| 	 | ||||
| 	if(nullptr == curr) | ||||
| 	{ | ||||
| 		env->complain("Source tile doesn't exist!"); | ||||
| 		return false; | ||||
| 	}	 | ||||
| 		 | ||||
| 	if(parameters.caster->movement <= 0) | ||||
| 	{ | ||||
| 		env->complain("Hero needs movement points to cast Dimension Door!"); | ||||
| 		return false; | ||||
| 	} | ||||
| 	 | ||||
| 	const int schoolLevel = parameters.caster->getSpellSchoolLevel(owner); | ||||
| 			 | ||||
| 	if(parameters.caster->getBonusesCount(Bonus::SPELL_EFFECT, SpellID::DIMENSION_DOOR) >= owner->getPower(schoolLevel)) //limit casts per turn | ||||
| 	{ | ||||
| 		InfoWindow iw; | ||||
| 		iw.player = parameters.caster->tempOwner; | ||||
| 		iw.text.addTxt(MetaString::GENERAL_TXT, 338); //%s is not skilled enough to cast this spell again today. | ||||
| 		iw.text.addReplacement(parameters.caster->name); | ||||
| 		env->sendAndApply(&iw); | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	GiveBonus gb; | ||||
| 	gb.id = parameters.caster->id.getNum(); | ||||
| 	gb.bonus = Bonus(Bonus::ONE_DAY, Bonus::NONE, Bonus::SPELL_EFFECT, 0, owner->id); | ||||
| 	env->sendAndApply(&gb); | ||||
|  | ||||
| 	if(!dest->isClear(curr)) //wrong dest tile | ||||
| 	{ | ||||
| 		InfoWindow iw; | ||||
| 		iw.player = parameters.caster->tempOwner; | ||||
| 		iw.text.addTxt(MetaString::GENERAL_TXT, 70); //Dimension Door failed! | ||||
| 		env->sendAndApply(&iw);		 | ||||
| 	} | ||||
| 	else if(env->moveHero(parameters.caster->id, parameters.pos + parameters.caster->getVisitableOffset(), true)) | ||||
| 	{ | ||||
| 		SetMovePoints smp; | ||||
| 		smp.hid = parameters.caster->id; | ||||
| 		smp.val = std::max<ui32>(0, parameters.caster->movement - 300); | ||||
| 		env->sendAndApply(&smp); | ||||
| 	} | ||||
| 	return true; 	 | ||||
| } | ||||
|  | ||||
| ///TownPortalMechanics | ||||
| bool TownPortalMechanics::applyAdventureEffects(const SpellCastEnvironment * env, AdventureSpellCastParameters& parameters) const | ||||
| { | ||||
| 	if (!env->getMap()->isInTheMap(parameters.pos)) | ||||
| 	{ | ||||
| 		env->complain("Destination tile not present!"); | ||||
| 		return false; | ||||
| 	}	 | ||||
|  | ||||
| 	TerrainTile tile = env->getMap()->getTile(parameters.pos); | ||||
| 	if (tile.visitableObjects.empty() || tile.visitableObjects.back()->ID != Obj::TOWN) | ||||
| 	{ | ||||
| 		env->complain("Town not found for Town Portal!");	 | ||||
| 		return false; | ||||
| 	}		 | ||||
|  | ||||
| 	CGTownInstance * town = static_cast<CGTownInstance*>(tile.visitableObjects.back()); | ||||
| 	if (town->tempOwner != parameters.caster->tempOwner) | ||||
| 	{ | ||||
| 		env->complain("Can't teleport to another player!"); | ||||
| 		return false; | ||||
| 	}			 | ||||
| 	 | ||||
| 	if (town->visitingHero) | ||||
| 	{ | ||||
| 		env->complain("Can't teleport to occupied town!"); | ||||
| 		return false; | ||||
| 	} | ||||
| 	 | ||||
| 	if (parameters.caster->getSpellSchoolLevel(owner) < 2) | ||||
| 	{ | ||||
| 		si32 dist = town->pos.dist2dSQ(parameters.caster->pos); | ||||
| 		ObjectInstanceID nearest = town->id; //nearest town's ID | ||||
| 		for(const CGTownInstance * currTown : env->getCb()->getPlayer(parameters.caster->tempOwner)->towns) | ||||
| 		{ | ||||
| 			si32 currDist = currTown->pos.dist2dSQ(parameters.caster->pos); | ||||
| 			if (currDist < dist) | ||||
| 			{ | ||||
| 				nearest = currTown->id; | ||||
| 				dist = currDist; | ||||
| 			} | ||||
| 		} | ||||
| 		if (town->id != nearest) | ||||
| 		{ | ||||
| 			env->complain("This hero can only teleport to nearest town!"); | ||||
| 			return false; | ||||
| 		} | ||||
| 			 | ||||
| 	} | ||||
| 	env->moveHero(parameters.caster->id, town->visitablePos() + parameters.caster->getVisitableOffset() ,1);	 | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
|  | ||||
| ///BATTLE SPELLS | ||||
|  | ||||
| ///AcidBreathDamageMechnics | ||||
| void AcidBreathDamageMechnics::applyBattleEffects(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters, SpellCastContext & ctx) const | ||||
| { | ||||
|   | ||||
| @@ -41,7 +41,7 @@ public: | ||||
| 	 | ||||
| 	virtual ESpellCastProblem::ESpellCastProblem isImmuneByStack(const CGHeroInstance * caster, const CStack * obj) const = 0; | ||||
|                     | ||||
| 	//virtual bool adventureCast(const SpellCastContext & context) const = 0;  | ||||
| 	virtual bool adventureCast(const SpellCastEnvironment * env, AdventureSpellCastParameters & parameters) const = 0;  | ||||
| 	virtual void battleCast(const SpellCastEnvironment * env, BattleSpellCastParameters & parameters) const = 0; 	 | ||||
| 	 | ||||
| 	static ISpellMechanics * createMechanics(CSpell * s); | ||||
|   | ||||
| @@ -67,8 +67,11 @@ public: | ||||
| 	void sendAndApply(CPackForClient * info) const override;	 | ||||
| 	CRandomGenerator & getRandomGenerator() const override; | ||||
| 	void complain(const std::string & problem) const override; | ||||
| 	const CMap * getMap() const override; | ||||
| 	const CGameInfoCallback * getCb() const override; | ||||
| 	bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, PlayerColor asker = PlayerColor::NEUTRAL) const override;	 | ||||
| private: | ||||
| 	CGameHandler * gh;	 | ||||
| 	mutable CGameHandler * gh;	 | ||||
| }; | ||||
|  | ||||
| CondSh<bool> battleMadeAction; | ||||
| @@ -4970,222 +4973,12 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat ) | ||||
| bool CGameHandler::castSpell(const CGHeroInstance *h, SpellID spellID, const int3 &pos) | ||||
| { | ||||
| 	const CSpell *s = spellID.toSpell(); | ||||
| 	int cost = h->getSpellCost(s); | ||||
| 	int schoolLevel = h->getSpellSchoolLevel(s); | ||||
|  | ||||
| 	if(!h->canCastThisSpell(s)) | ||||
| 		COMPLAIN_RET("Hero cannot cast this spell!"); | ||||
| 	if(h->mana < cost) | ||||
| 		COMPLAIN_RET("Hero doesn't have enough spell points to cast this spell!"); | ||||
| 	if(s->combatSpell) | ||||
| 		COMPLAIN_RET("This function can be used only for adventure map spells!"); | ||||
|  | ||||
| 	AdvmapSpellCast asc; | ||||
| 	asc.caster = h; | ||||
| 	asc.spellID = spellID; | ||||
| 	sendAndApply(&asc); | ||||
|  | ||||
| 	switch(spellID) | ||||
| 	{ | ||||
| 	case SpellID::SUMMON_BOAT: | ||||
| 		{ | ||||
| 			//check if spell works at all | ||||
| 			if(gs->getRandomGenerator().nextInt(99) >= s->getPower(schoolLevel)) //power is % chance of success | ||||
| 			{ | ||||
| 				InfoWindow iw; | ||||
| 				iw.player = h->tempOwner; | ||||
| 				iw.text.addTxt(MetaString::GENERAL_TXT, 336); //%s tried to summon a boat, but failed. | ||||
| 				iw.text.addReplacement(h->name); | ||||
| 				sendAndApply(&iw); | ||||
| 				break; | ||||
| 			} | ||||
|  | ||||
| 			//try to find unoccupied boat to summon | ||||
| 			const CGBoat *nearest = nullptr; | ||||
| 			double dist = 0; | ||||
| 			int3 summonPos = h->bestLocation(); | ||||
| 			if(summonPos.x < 0) | ||||
| 				COMPLAIN_RET("There is no water tile available!"); | ||||
|  | ||||
| 			for(const CGObjectInstance *obj : gs->map->objects) | ||||
| 			{ | ||||
| 				if(obj && obj->ID == Obj::BOAT) | ||||
| 				{ | ||||
| 					const CGBoat *b = static_cast<const CGBoat*>(obj); | ||||
| 					if(b->hero) continue; //we're looking for unoccupied boat | ||||
|  | ||||
| 					double nDist = distance(b->pos, h->getPosition()); | ||||
| 					if(!nearest || nDist < dist) //it's first boat or closer than previous | ||||
| 					{ | ||||
| 						nearest = b; | ||||
| 						dist = nDist; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if(nearest) //we found boat to summon | ||||
| 			{ | ||||
| 				ChangeObjPos cop; | ||||
| 				cop.objid = nearest->id; | ||||
| 				cop.nPos = summonPos + int3(1,0,0);; | ||||
| 				cop.flags = 1; | ||||
| 				sendAndApply(&cop); | ||||
| 			} | ||||
| 			else if(schoolLevel < 2) //none or basic level -> cannot create boat :( | ||||
| 			{ | ||||
| 				InfoWindow iw; | ||||
| 				iw.player = h->tempOwner; | ||||
| 				iw.text.addTxt(MetaString::GENERAL_TXT, 335); //There are no boats to summon. | ||||
| 				sendAndApply(&iw); | ||||
| 			} | ||||
| 			else //create boat | ||||
| 			{ | ||||
| 				NewObject no; | ||||
| 				no.ID = Obj::BOAT; | ||||
| 				no.subID = h->getBoatType(); | ||||
| 				no.pos = summonPos + int3(1,0,0);; | ||||
| 				sendAndApply(&no); | ||||
| 			} | ||||
| 			break; | ||||
| 		} | ||||
|  | ||||
| 	case SpellID::SCUTTLE_BOAT: | ||||
| 		{ | ||||
| 			//check if spell works at all | ||||
| 			if(gs->getRandomGenerator().nextInt(99) >= s->getPower(schoolLevel)) //power is % chance of success | ||||
| 			{ | ||||
| 				InfoWindow iw; | ||||
| 				iw.player = h->tempOwner; | ||||
| 				iw.text.addTxt(MetaString::GENERAL_TXT, 337); //%s tried to scuttle the boat, but failed | ||||
| 				iw.text.addReplacement(h->name); | ||||
| 				sendAndApply(&iw); | ||||
| 				break; | ||||
| 			} | ||||
| 			if(!gs->map->isInTheMap(pos)) | ||||
| 				COMPLAIN_RET("Invalid dst tile for scuttle!"); | ||||
|  | ||||
| 			//TODO: test range, visibility | ||||
| 			const TerrainTile *t = &gs->map->getTile(pos); | ||||
| 			if(!t->visitableObjects.size() || t->visitableObjects.back()->ID != Obj::BOAT) | ||||
| 				COMPLAIN_RET("There is no boat to scuttle!"); | ||||
|  | ||||
| 			RemoveObject ro; | ||||
| 			ro.id = t->visitableObjects.back()->id; | ||||
| 			sendAndApply(&ro); | ||||
| 			break; | ||||
| 		} | ||||
| 	case SpellID::DIMENSION_DOOR: | ||||
| 		{ | ||||
| 			const TerrainTile *dest = getTile(pos); | ||||
| 			const TerrainTile *curr = getTile(h->getSightCenter()); | ||||
|  | ||||
| 			if(!dest) | ||||
| 				COMPLAIN_RET("Destination tile doesn't exist!"); | ||||
| 			if(!h->movement) | ||||
| 				COMPLAIN_RET("Hero needs movement points to cast Dimension Door!"); | ||||
| 			if(h->getBonusesCount(Bonus::SPELL_EFFECT, SpellID::DIMENSION_DOOR) >= s->getPower(schoolLevel)) //limit casts per turn | ||||
| 			{ | ||||
| 				InfoWindow iw; | ||||
| 				iw.player = h->tempOwner; | ||||
| 				iw.text.addTxt(MetaString::GENERAL_TXT, 338); //%s is not skilled enough to cast this spell again today. | ||||
| 				iw.text.addReplacement(h->name); | ||||
| 				sendAndApply(&iw); | ||||
| 				break; | ||||
| 			} | ||||
|  | ||||
| 			GiveBonus gb; | ||||
| 			gb.id = h->id.getNum(); | ||||
| 			gb.bonus = Bonus(Bonus::ONE_DAY, Bonus::NONE, Bonus::SPELL_EFFECT, 0, SpellID::DIMENSION_DOOR); | ||||
| 			sendAndApply(&gb); | ||||
|  | ||||
| 			if(!dest->isClear(curr)) //wrong dest tile | ||||
| 			{ | ||||
| 				InfoWindow iw; | ||||
| 				iw.player = h->tempOwner; | ||||
| 				iw.text.addTxt(MetaString::GENERAL_TXT, 70); //Dimension Door failed! | ||||
| 				sendAndApply(&iw); | ||||
| 				break; | ||||
| 			} | ||||
|  | ||||
| 			if (moveHero(h->id, pos + h->getVisitableOffset(), true)) | ||||
| 			{ | ||||
| 				SetMovePoints smp; | ||||
| 				smp.hid = h->id; | ||||
| 				smp.val = std::max<ui32>(0, h->movement - 300); | ||||
| 				sendAndApply(&smp); | ||||
| 			} | ||||
| 		} | ||||
| 		break; | ||||
| 	case SpellID::FLY: | ||||
| 		{ | ||||
| 			int subtype = schoolLevel >= 2 ? 1 : 2; //adv or expert | ||||
|  | ||||
| 			GiveBonus gb; | ||||
| 			gb.id = h->id.getNum(); | ||||
| 			gb.bonus = Bonus(Bonus::ONE_DAY, Bonus::FLYING_MOVEMENT, Bonus::SPELL_EFFECT, 0, SpellID::FLY, subtype); | ||||
| 			sendAndApply(&gb); | ||||
| 		} | ||||
| 		break; | ||||
| 	case SpellID::WATER_WALK: | ||||
| 		{ | ||||
| 			int subtype = schoolLevel >= 2 ? 1 : 2; //adv or expert | ||||
|  | ||||
| 			GiveBonus gb; | ||||
| 			gb.id = h->id.getNum(); | ||||
| 			gb.bonus = Bonus(Bonus::ONE_DAY, Bonus::WATER_WALKING, Bonus::SPELL_EFFECT, 0, SpellID::WATER_WALK, subtype); | ||||
| 			sendAndApply(&gb); | ||||
| 		} | ||||
| 		break; | ||||
|  | ||||
| 	case SpellID::TOWN_PORTAL: | ||||
| 		{ | ||||
| 			if (!gs->map->isInTheMap(pos)) | ||||
| 				COMPLAIN_RET("Destination tile not present!") | ||||
| 			TerrainTile tile = gs->map->getTile(pos); | ||||
| 			if (tile.visitableObjects.empty() || tile.visitableObjects.back()->ID != Obj::TOWN ) | ||||
| 				COMPLAIN_RET("Town not found for Town Portal!"); | ||||
|  | ||||
| 			CGTownInstance * town = static_cast<CGTownInstance*>(tile.visitableObjects.back()); | ||||
| 			if (town->tempOwner != h->tempOwner) | ||||
| 				COMPLAIN_RET("Can't teleport to another player!"); | ||||
| 			if (town->visitingHero) | ||||
| 				COMPLAIN_RET("Can't teleport to occupied town!"); | ||||
|  | ||||
| 			if (h->getSpellSchoolLevel(s) < 2) | ||||
| 			{ | ||||
| 				si32 dist = town->pos.dist2dSQ(h->pos); | ||||
| 				ObjectInstanceID nearest = town->id; //nearest town's ID | ||||
| 				for(const CGTownInstance * currTown : gs->getPlayer(h->tempOwner)->towns) | ||||
| 				{ | ||||
| 					si32 currDist = currTown->pos.dist2dSQ(h->pos); | ||||
| 					if (currDist < dist) | ||||
| 					{ | ||||
| 						nearest = currTown->id; | ||||
| 						dist = currDist; | ||||
| 					} | ||||
| 				} | ||||
| 				if (town->id != nearest) | ||||
| 					COMPLAIN_RET("This hero can only teleport to nearest town!") | ||||
| 			} | ||||
| 			moveHero(h->id, town->visitablePos() + h->getVisitableOffset() ,1); | ||||
| 		} | ||||
| 		break; | ||||
|  | ||||
| 	case SpellID::VISIONS: | ||||
| 	case SpellID::VIEW_EARTH: | ||||
| 	case SpellID::DISGUISE: | ||||
| 	case SpellID::VIEW_AIR: | ||||
| 	default: | ||||
| 		COMPLAIN_RET("This spell is not implemented yet!"); | ||||
| 	} | ||||
|  | ||||
| 	SetMana sm; | ||||
| 	sm.hid = h->id; | ||||
| 	sm.absolute = false; | ||||
| 	sm.val = -cost; | ||||
| 	sendAndApply(&sm); | ||||
|  | ||||
| 	return true; | ||||
| 	 | ||||
| 	AdventureSpellCastParameters p; | ||||
| 	p.caster = h; | ||||
| 	p.pos = pos; | ||||
| 	 | ||||
| 	return s->adventureCast(spellEnv, p); | ||||
| } | ||||
|  | ||||
| void CGameHandler::visitObjectOnTile(const TerrainTile &t, const CGHeroInstance * h) | ||||
| @@ -6010,7 +5803,7 @@ CGameHandler::FinishingBattleHelper::FinishingBattleHelper() | ||||
| 	winnerHero = loserHero = nullptr; | ||||
| } | ||||
|  | ||||
|  | ||||
| ///ServerSpellCastEnvironment | ||||
| ServerSpellCastEnvironment::ServerSpellCastEnvironment(CGameHandler * gh): gh(gh) | ||||
| { | ||||
| 	 | ||||
| @@ -6030,3 +5823,20 @@ void ServerSpellCastEnvironment::complain(const std::string& problem) const | ||||
| { | ||||
| 	gh->complain(problem); | ||||
| } | ||||
|  | ||||
| const CGameInfoCallback * ServerSpellCastEnvironment::getCb() const | ||||
| { | ||||
| 	return gh; | ||||
| } | ||||
|  | ||||
|  | ||||
| const CMap * ServerSpellCastEnvironment::getMap() const | ||||
| { | ||||
| 	return gh->gameState()->map; | ||||
| } | ||||
|  | ||||
| bool ServerSpellCastEnvironment::moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, PlayerColor asker) const | ||||
| { | ||||
| 	return gh->moveHero(hid, dst, teleporting, asker); | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user