Przeglądaj źródła

Implemented Summon Boat spell.

Michał W. Urbańczyk 15 lat temu
rodzic
commit
ad841f7fea

+ 10 - 1
CCallback.cpp

@@ -121,7 +121,7 @@ int CCallback::getSpellCost(const CSpell * sp, const CGHeroInstance * caster) co
 		return gs->curB->getSpellCost(sp, caster);
 
 	//if there is no battle
-	return sp->costs[caster->getSpellSchoolLevel(sp)];
+	return caster->getSpellCost(sp);
 }
 
 int CCallback::estimateSpellDamage(const CSpell * sp) const
@@ -954,6 +954,15 @@ si8 CCallback::battleGetStackLuck( int stackID )
 	return gs->curB->Luck( gs->curB->getStack(stackID) );
 }
 
+void CCallback::castSpell(const CGHeroInstance *hero, int spellID, const int3 &pos)
+{
+	CastAdvSpell cas;
+	cas.hid = hero->id;
+	cas.sid = spellID;
+	cas.pos = pos;
+	sendRequest(&cas);
+}
+
 InfoAboutTown::InfoAboutTown()
 {
 	tType = NULL;

+ 2 - 0
CCallback.h

@@ -128,6 +128,7 @@ public:
 	virtual void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out, int3 src = int3(-1,-1,-1), int movement = -1) =0;
 	virtual void recalculatePaths()=0; //updates main, client pathfinder info (should be called when moving hero is over)
 	virtual void dig(const CGObjectInstance *hero)=0; 
+	virtual void castSpell(const CGHeroInstance *hero, int spellID, const int3 &pos = int3(-1, -1, -1))=0; //cast adventure map spell
 	
 	//map
 	virtual std::vector < const CGObjectInstance * > getBlockingObjs(int3 pos)const =0;
@@ -227,6 +228,7 @@ public:
 	void sendMessage(const std::string &mess);
 	void buildBoat(const IShipyard *obj);
 	void dig(const CGObjectInstance *hero); 
+	void castSpell(const CGHeroInstance *hero, int spellID, const int3 &pos = int3(-1, -1, -1));
 
 //get info
 	bool verifyPath(CPath * path, bool blockSea) const;

+ 25 - 4
client/CSpellWindow.cpp

@@ -668,7 +668,8 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
 {
 	if(!down && mySpell!=-1)
 	{
-		int spellCost = owner->myInt->cb->getSpellCost(&CGI->spellh->spells[mySpell], owner->myHero);
+		const CSpell *sp = &CGI->spellh->spells[mySpell];
+		int spellCost = owner->myInt->cb->getSpellCost(sp, owner->myHero);
 		//we will cast a spell
 		if(owner->myInt->battleInt && owner->myInt->cb->battleCanCastSpell() && spellCost <= owner->myHero->mana) //if battle window is open
 		{
@@ -676,15 +677,35 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
 			owner->fexitb();
 			owner->myInt->battleInt->castThisSpell(spell);
 		}
-		else
+		else //adventure spell
 		{
 			//insufficient mana
 			if(spellCost > owner->myHero->mana)
 			{
-				std::vector<SComponent*> comps;
 				char msgBuf[500];
 				sprintf(msgBuf, CGI->generaltexth->allTexts[206].c_str(), spellCost, owner->myHero->mana);
-				owner->myInt->showInfoDialog(std::string(msgBuf), comps);
+				owner->myInt->showInfoDialog(std::string(msgBuf));
+			}
+			else
+			{
+				int spell = mySpell;
+				const CGHeroInstance *h = owner->myHero;
+				owner->fexitb();
+
+				switch(spell)
+				{
+				case 0: //summon boat
+					{
+						int3 pos = h->bestLocation();
+						if(pos.x < 0)
+						{
+							LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[334]); //There is no place to put the boat.
+							return;
+						}
+					}
+				}
+
+				LOCPLINT->cb->castSpell(h, spell);
 			}
 		}
 	}

+ 6 - 0
client/Client.cpp

@@ -511,5 +511,11 @@ void CClient::handlePack( CPack * pack )
 	delete pack;
 }
 
+void CClient::updatePaths()
+{	
+	const CGHeroInstance *h = getHero(getSelectedHero());
+	if (h)//if we have selected hero...
+		gs->calculatePaths(h, *pathInfo);
+}
 template void CClient::serialize( CISer<CLoadFile> &h, const int version );
 template void CClient::serialize( COSer<CSaveFile> &h, const int version );

+ 1 - 0
client/Client.h

@@ -113,6 +113,7 @@ public:
 	void waitForServer();
 	CPack * retreivePack(); //gets from server next pack (allocates it with new)
 	void handlePack( CPack * pack ); //applies the given pack and deletes it
+	void updatePaths();
 
 	//////////////////////////////////////////////////////////////////////////
 

+ 3 - 2
client/NetPacksClient.cpp

@@ -107,8 +107,7 @@ void FoWChange::applyCl( CClient *cl )
 	else
 		cl->playerint[player]->tileHidden(tiles);
 
-	if (cl->IGameCallback::getSelectedHero(player))//if we have selected hero...
-		GS(cl)->calculatePaths(cl->IGameCallback::getSelectedHero(player), *cl->pathInfo);
+	cl->updatePaths();
 }
 
 void SetAvailableHeroes::applyCl( CClient *cl )
@@ -146,6 +145,8 @@ void ChangeObjPos::applyCl( CClient *cl )
 	CGObjectInstance *obj = GS(cl)->map->objects[objid];
 	if(flags & 1)
 		CGI->mh->printObject(obj);
+
+	cl->updatePaths();
 }
 
 void PlayerEndsGame::applyCl( CClient *cl )

+ 55 - 21
hch/CObjectHandler.cpp

@@ -47,7 +47,6 @@ BankConfig CGPyramid::pyramidConfig;
 ui8 CGObelisk::obeliskCount; //how many obelisks are on map
 std::map<ui8, ui8> CGObelisk::visited; //map: color_id => how many obelisks has been visited
 
-
 void IObjectInterface::onHeroVisit(const CGHeroInstance * h) const 
 {};
 
@@ -687,6 +686,7 @@ int CGHeroInstance::getSpellSecLevel(int spell) const
 }
 
 CGHeroInstance::CGHeroInstance()
+ : IBoatGenerator(this)
 {
 	ID = HEROI_TYPE;
 	tacticFormationEnabled = inTownGarrison = false;
@@ -1226,6 +1226,34 @@ const BonusList * CGHeroInstance::ownerBonuses() const
 		return &p->bonuses;
 }
 
+int CGHeroInstance::getBoatType() const
+{
+	int alignment = type->heroType / 6; 
+	switch(alignment)
+	{
+	case 0:
+		return 1; //good
+	case 1:
+		return 0; //evil
+	case 2:
+		return 2;
+	default:
+		assert(0);
+	}
+}
+
+void CGHeroInstance::getOutOffsets(std::vector<int3> &offsets) const
+{
+	static int3 dirs[] = { int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0), int3(1,1,0),int3(-1,1,0),int3(1,-1,0),int3(-1,-1,0) };
+	for (size_t i = 0; i < ARRAY_COUNT(dirs); i++)
+		offsets += dirs[i];
+}
+
+int CGHeroInstance::getSpellCost(const CSpell *sp) const
+{
+	return sp->costs[getSpellSchoolLevel(sp)];
+}
+
 void CGDwelling::initObj()
 {
 	switch(ID)
@@ -5308,13 +5336,6 @@ void CGSirens::onHeroVisit( const CGHeroInstance * h ) const
 	cb->showInfoDialog(&iw);
 }
 
-void IShipyard::getBoatCost( std::vector<si32> &cost ) const
-{
-	cost.resize(RESOURCE_QUANTITY);
-	cost[0] = 10;
-	cost[6] = 1000;
-}
-
 //bool IShipyard::validLocation() const
 //{
 //	std::vector<int3> offsets;
@@ -5327,7 +5348,7 @@ void IShipyard::getBoatCost( std::vector<si32> &cost ) const
 //	return false;
 //}
 
-int3 IShipyard::bestLocation() const
+int3 IBoatGenerator::bestLocation() const
 {
 	std::vector<int3> offsets;
 	getOutOffsets(offsets);
@@ -5339,12 +5360,7 @@ int3 IShipyard::bestLocation() const
 	return int3(-1,-1,-1);
 }
 
-IShipyard::IShipyard(const CGObjectInstance *O) 
-	: o(O)
-{
-}
-
-int IShipyard::state() const
+int IBoatGenerator::state() const
 {
 	int3 tile = bestLocation();
 	TerrainTile *t = IObjectInterface::cb->getTile(tile);
@@ -5358,6 +5374,30 @@ int IShipyard::state() const
 		return 2; //blocked
 }
 
+int IBoatGenerator::getBoatType() const
+{
+	//We make good ships by default
+	return 1;
+}
+
+
+IBoatGenerator::IBoatGenerator(const CGObjectInstance *O) 
+: o(O)
+{
+}
+
+void IShipyard::getBoatCost( std::vector<si32> &cost ) const
+{
+	cost.resize(RESOURCE_QUANTITY);
+	cost[0] = 10;
+	cost[6] = 1000;
+}
+
+IShipyard::IShipyard(const CGObjectInstance *O) 
+	: IBoatGenerator(O)
+{
+}
+
 IShipyard * IShipyard::castFrom( CGObjectInstance *obj )
 {
 	if(obj->ID == TOWNI_TYPE)
@@ -5380,12 +5420,6 @@ const IShipyard * IShipyard::castFrom( const CGObjectInstance *obj )
 	return castFrom(const_cast<CGObjectInstance*>(obj));
 }
 
-int IShipyard::getBoatType() const
-{
-	//We make good ships by default
-	return 1;
-}
-
 CGShipyard::CGShipyard()
 	:IShipyard(this)
 {

+ 17 - 5
hch/CObjectHandler.h

@@ -115,18 +115,23 @@ public:
 	static void postInit();//caleed after objs receive their initObj
 };
 
-class DLL_EXPORT IShipyard
+class DLL_EXPORT IBoatGenerator
 {
 public:
 	const CGObjectInstance *o;
 
-	IShipyard(const CGObjectInstance *O);
-	virtual void getBoatCost(std::vector<si32> &cost) const;
+	IBoatGenerator(const CGObjectInstance *O);
 	virtual int getBoatType() const; //0 - evil (if a ship can be evil...?), 1 - good, 2 - neutral
 	virtual void getOutOffsets(std::vector<int3> &offsets) const =0; //offsets to obj pos when we boat can be placed
-	//virtual bool validLocation() const; //returns true if there is a water tile near where boat can be placed
 	int3 bestLocation() const; //returns location when the boat should be placed
 	int state() const; //0 - can buid, 1 - there is already a boat at dest tile, 2 - dest tile is blocked, 3 - no water
+};
+
+class DLL_EXPORT IShipyard : public IBoatGenerator
+{
+public:
+	IShipyard(const CGObjectInstance *O);
+	virtual void getBoatCost(std::vector<si32> &cost) const;
 
 	static const IShipyard *castFrom(const CGObjectInstance *obj);
 	static IShipyard *castFrom(CGObjectInstance *obj);
@@ -227,7 +232,7 @@ public:
 	}
 };
 
-class DLL_EXPORT CGHeroInstance : public CArmedInstance
+class DLL_EXPORT CGHeroInstance : public CArmedInstance, public IBoatGenerator
 {
 public:
 	//////////////////////////////////////////////////////////////////////////
@@ -287,6 +292,12 @@ public:
 	int3 getSightCenter() const; //"center" tile from which the sight distance is calculated
 	int getSightRadious() const; //sight distance (should be used if player-owned structure)
 
+
+	//////////////////////////////////////////////////////////////////////////
+
+	int getBoatType() const; //0 - evil (if a ship can be evil...?), 1 - good, 2 - neutral
+	void getOutOffsets(std::vector<int3> &offsets) const; //offsets to obj pos when we boat can be placed
+
 	//////////////////////////////////////////////////////////////////////////
 
 	const std::string &getBiography() const;
@@ -298,6 +309,7 @@ public:
 	si32 manaRegain() const; //how many points of mana can hero regain "naturally" in one day
 	bool canWalkOnSea() const;
 	int getCurrentLuck(int stack=-1, bool town=false) const;
+	int getSpellCost(const CSpell *sp) const; //do not use during battles -> bonuses from army would be ignored
 
 	const BonusList *ownerBonuses() const;
 	const HeroBonus *getBonus(int from, int id) const;

+ 1 - 1
lib/CGameState.cpp

@@ -2740,7 +2740,7 @@ CStack * BattleInfo::generateNewStack(const CGHeroInstance * owner, int creature
 
 ui32 BattleInfo::getSpellCost(const CSpell * sp, const CGHeroInstance * caster) const
 {
-	ui32 ret = VLC->spellh->spells[sp->id].costs[caster->getSpellSchoolLevel(sp)];
+	ui32 ret = caster->getSpellCost(sp);
 
 	//checking for friendly stacks reducing cost of the spell and
 	//enemy stacks increasing it

+ 22 - 3
lib/NetPacks.h

@@ -380,7 +380,11 @@ struct GiveBonus :  public CPackForClient //115
 
 struct ChangeObjPos : public CPackForClient //116
 {
-	ChangeObjPos(){type = 116;};
+	ChangeObjPos()
+	{
+		type = 116; 
+		flags = 0;
+	}
 	void applyFirstCl(CClient *cl);
 	void applyCl(CClient *cl);
 	DLL_EXPORT void applyGs(CGameState *gs);
@@ -692,10 +696,11 @@ struct InfoWindow : public CPackForClient //103  - displays simple info window
 	{
 		h & text & components & player & soundID;
 	}
-	InfoWindow() { 
+	InfoWindow() 
+	{ 
 		type = 103;
 		soundID = 0; 
-	};
+	}
 };
 
 struct SetObjectProperty : public CPackForClient//1001
@@ -1424,6 +1429,20 @@ struct DigWithHero : public CPackForServer
 	}
 };
 
+struct CastAdvSpell : public CPackForServer
+{
+	CastAdvSpell(){}
+	si32 hid; //hero id
+	ui32 sid; //spell id
+	int3 pos; //selected tile (not always used)
+
+	bool applyGh(CGameHandler *gh);
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & hid & sid & pos;
+	}
+};
+
 /***********************************************************************************************************/
 
 struct SaveGame : public CPackForClient, public CPackForServer

+ 1 - 0
lib/RegisterTypes.cpp

@@ -153,6 +153,7 @@ void registerTypes3(Serializer &s)
 	s.template registerType<MakeAction>();
 	s.template registerType<MakeCustomAction>();
 	s.template registerType<DigWithHero>();
+	s.template registerType<CastAdvSpell>();
 
 	s.template registerType<SaveGame>();
 	s.template registerType<SetSelection>();

+ 100 - 0
server/CGameHandler.cpp

@@ -46,6 +46,8 @@ extern bool end2;
 #ifdef max
 #undef max
 #endif
+
+#define COMPLAIN_RET(txt) {complain(txt); return false;}
 #define NEW_ROUND 		BattleNextRound bnr;\
 		bnr.round = gs->curB->round + 1;\
 		sendAndApply(&bnr);
@@ -4197,3 +4199,101 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
 		}
 	}
 }
+
+bool CGameHandler::castSpell(const CGHeroInstance *h, int spellID, const int3 &pos)
+{
+	const CSpell *s = &VLC->spellh->spells[spellID];
+	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!");
+
+	switch(spellID)
+	{
+	case 0: //Summon Boat 
+		{
+			//check if spell works at all
+			if(rand() % 100 >= s->powers[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);
+				return true; //TODO? or should it be false? request was correct and realized, but spell failed...
+			}
+
+			//try to find unoccupied boat to summon
+			const CGBoat *nearest = NULL;
+			double dist = 0;
+			int3 summonPos = h->bestLocation();
+			if(summonPos.x < 0)
+				COMPLAIN_RET("There is no water tile available!");
+
+			BOOST_FOREACH(const CGObjectInstance *obj, gs->map->objects)
+			{
+				if(obj && obj->ID == 8)
+				{
+					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 = 8;
+				no.subID = h->getBoatType();
+				no.pos = summonPos + int3(1,0,0);;
+				sendAndApply(&no);
+			}
+			break;
+		}
+
+	case 1: //Scuttle Boat 
+	case 2: //Visions 
+	case 3: //View Earth 
+	case 4: //Disguise 
+	case 5: //View Air 
+	case 6: //Fly 
+	case 7: //Water Walk 
+	case 8: //Dimension Door
+	case 9: //Town Portal 
+	default:
+		COMPLAIN_RET("This spell is not implemented yet!");
+	}
+
+	SetMana sm;
+	sm.hid = h->id;
+	sm.val = h->mana - cost;
+	sendAndApply(&sm);
+
+	return true;
+}

+ 1 - 0
server/CGameHandler.h

@@ -181,6 +181,7 @@ public:
 	void objectVisited( const CGObjectInstance * obj, const CGHeroInstance * h );
 	void engageIntoBattle( ui8 player );
 	bool dig(const CGHeroInstance *h);
+	bool castSpell(const CGHeroInstance *h, int spellID, const int3 &pos);
 
 	template <typename Handler> void serialize(Handler &h, const int version)
 	{

+ 6 - 0
server/NetPacksServer.cpp

@@ -160,6 +160,12 @@ bool DigWithHero::applyGh( CGameHandler *gh )
 	return gh->dig(gh->getHero(id));
 }
 
+bool CastAdvSpell::applyGh( CGameHandler *gh )
+{
+	ERROR_IF_NOT_OWNS(hid);
+	return gh->castSpell(gh->getHero(hid), sid, pos);
+}
+
 bool PlayerMessage::applyGh( CGameHandler *gh )
 {
 	if(gh->getPlayerAt(c) != player) ERROR_AND_RETURN;