Переглянути джерело

Fourth part of sailing code.
Support for Shipyard (both for town and adventure map versions). Improved boat displaying code. Minor changes.

Michał W. Urbańczyk 16 роки тому
батько
коміт
9fd4b5bb62

+ 7 - 0
CCallback.cpp

@@ -732,6 +732,13 @@ void CCallback::sendMessage(const std::string &mess)
 	*cl->serv << &pm;
 }
 
+void CCallback::buildBoat( const IShipyard *obj )
+{
+	BuildBoat bb;
+	bb.objid = obj->o->id;
+	*cl->serv << &bb;
+}
+
 InfoAboutHero::InfoAboutHero()
 {
 	details = NULL;

+ 3 - 0
CCallback.h

@@ -37,6 +37,7 @@ struct lua_State;
 class CClient;
 struct TerrainTile;
 class CHeroClass;
+class IShipyard;
 
 struct InfoAboutHero
 {
@@ -102,6 +103,7 @@ public:
 	virtual void recruitHero(const CGTownInstance *town, const CGHeroInstance *hero)=0;
 	virtual void save(const std::string &fname) = 0;
 	virtual void sendMessage(const std::string &mess) = 0;
+	virtual void buildBoat(const IShipyard *obj) = 0;
 
 //get info
 	virtual bool verifyPath(CPath * path, bool blockSea)const =0;
@@ -196,6 +198,7 @@ public:
 	void recruitHero(const CGTownInstance *town, const CGHeroInstance *hero);
 	void save(const std::string &fname);
 	void sendMessage(const std::string &mess);
+	void buildBoat(const IShipyard *obj);
 
 //get info
 	bool verifyPath(CPath * path, bool blockSea) const;

+ 3 - 0
CGameInterface.h

@@ -31,6 +31,7 @@ class CGObjectInstance;
 class CGDwelling;
 class CCreatureSet;
 class CArmedInstance;
+class IShipyard;
 struct BattleResult;
 struct BattleAttack;
 struct BattleStackAttacked;
@@ -83,12 +84,14 @@ public:
 	virtual void receivedResource(int type, int val){};
 	virtual void showInfoDialog(const std::string &text, const std::vector<Component*> &components, int soundID){};
 	virtual void showRecruitmentDialog(const CGDwelling *dwelling, int level){}
+	virtual void showShipyardDialog(const IShipyard *obj){} //obj may be town or shipyard; state: 0 - can buid, 1 - lack of resources, 2 - dest tile is blocked, 3 - no water
 	//virtual void showSelDialog(const std::string &text, const std::vector<Component*> &components, ui32 askID){};
 	//virtual void showYesNoDialog(const std::string &text, const std::vector<Component*> &components, ui32 askID){};
 	virtual void showBlockingDialog(const std::string &text, const std::vector<Component> &components, ui32 askID, const int soundID, bool selection, bool cancel) = 0; //Show a dialog, player must take decision. If selection then he has to choose between one of given components, if cancel he is allowed to not choose. After making choice, CCallback::selectionMade should be called with number of selected component (1 - n) or 0 for cancel (if allowed) and askID.
 	virtual void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, boost::function<void()> &onEnd) = 0; //all stacks operations between these objects become allowed, interface has to call onEnd when done
 	virtual void tileHidden(const std::set<int3> &pos){};
 	virtual void tileRevealed(const std::set<int3> &pos){};
+	virtual void newObject(const CGObjectInstance * obj){}; //eg. ship built in shipyard
 	virtual void yourTurn(){};
 	virtual void availableCreaturesChanged(const CGTownInstance *town){};
 	virtual void heroBonusChanged(const CGHeroInstance *hero, const HeroBonus &bonus, bool gain){};//if gain hero received bonus, else he lost it

+ 26 - 2
client/CCastleInterface.cpp

@@ -393,6 +393,7 @@ public:
 CCastleInterface::CCastleInterface(const CGTownInstance * Town, int listPos)
 :hslotup(241,387,0,Town->garrisonHero,this),hslotdown(241,483,1,Town->visitingHero,this)
 {
+	showing = false;
 	bars = CDefHandler::giveDefEss("TPTHBAR.DEF");
 	status = CDefHandler::giveDefEss("TPTHCHK.DEF");
 	LOCPLINT->castleInt = this;
@@ -579,7 +580,11 @@ void CCastleInterface::buildingClicked(int building)
 
 				break;
 			}
-		//TODO: case 6: //shipyard
+		case 6: //shipyard
+			{
+				LOCPLINT->showShipyardDialog(town);
+				break;
+			}
 		case 7: case 8: case 9: //fort/citadel/castle
 			{
 				CFortScreen *fs = new CFortScreen(this);
@@ -622,7 +627,9 @@ void CCastleInterface::buildingClicked(int building)
 		//TODO: case 17: //special 1
 		//TODO: case 18: //basic horde 1
 		//TODO: case 19: //upg horde 1
-		//TODO: case 20: //ship at shipyard
+		case 20: //ship at shipyard
+			//Do nothing.
+			break;
 		//TODO: case 21: //special 2
 		case 22: //special 3
 			{
@@ -886,6 +893,19 @@ void CCastleInterface::recreateBuildings()
 		else
 			break;
 	}
+
+	//ship in shipyard
+	if(vstd::contains(town->builtBuildings,6))
+	{
+		std::vector <const CGObjectInstance *> vobjs = LOCPLINT->cb->getVisitableObjs(town->bestLocation());
+		if(vobjs.size() && vobjs.front()->ID == 8) //there is visitable obj at shipyard output tile and it's a boat
+		{
+			Structure * st = CGI->townh->structures[town->subID][20];
+			buildings.push_back(new CBuildingRect(st));
+			s.insert(std::pair<int,int>(st->group,st->ID));
+		}
+	}
+
 	std::sort(buildings.begin(),buildings.end(),srthlp);
 
 	//code for Mana Vortex (there are two sets of animation frames - one without mage guild and one with
@@ -934,6 +954,10 @@ void CCastleInterface::recreateBuildings()
 			shipyard->max = 1;
 		}
 	}
+
+	if(showing)
+		for(size_t i=0;i<buildings.size();i++)
+			buildings[i]->activate();
 }
 
 CRecruitmentWindow * CCastleInterface::showRecruitmentWindow( int building )

+ 25 - 0
client/CPlayerInterface.cpp

@@ -1726,6 +1726,7 @@ const CGHeroInstance * CPlayerInterface::getWHero( int pos )
 void CPlayerInterface::showRecruitmentDialog(const CGDwelling *dwelling, int level)
 {
 	waitWhileDialog();
+	boost::unique_lock<boost::recursive_mutex> un(*pim);
 	std::vector<std::pair<int,int> > cres;
 	for(int i = 0; i < dwelling->creatures.size(); i++)
 	{
@@ -1744,6 +1745,30 @@ void CPlayerInterface::waitWhileDialog()
 		showingDialog->cond.wait(un);
 }
 
+void CPlayerInterface::showShipyardDialog(const IShipyard *obj)
+{
+	boost::unique_lock<boost::recursive_mutex> un(*pim);
+	int state = obj->state();
+	std::vector<si32> cost;
+	obj->getBoatCost(cost);
+	CShipyardWindow *csw = new CShipyardWindow(cost, state, boost::bind(&CCallback::buildBoat, cb, obj));
+	pushInt(csw);
+}
+
+void CPlayerInterface::newObject( const CGObjectInstance * obj )
+{
+	boost::unique_lock<boost::recursive_mutex> un(*pim);
+	CGI->mh->printObject(obj);
+	//we might have built a boat in shipyard in opened town screen
+	if(obj->ID == 8 
+		&& LOCPLINT->castleInt  
+		&&  obj->pos-obj->getVisitableOffset() == LOCPLINT->castleInt->town->bestLocation())
+	{
+		CGI->soundh->playSound(soundBase::newBuilding);
+		LOCPLINT->castleInt->recreateBuildings();
+	}
+}
+
 void SystemOptions::setMusicVolume( int newVolume )
 {
 	musicVolume = newVolume;

+ 3 - 1
client/CPlayerInterface.h

@@ -163,10 +163,12 @@ public:
 	void receivedResource(int type, int val);
 	void showInfoDialog(const std::string &text, const std::vector<Component*> &components, int soundID);
 	void showRecruitmentDialog(const CGDwelling *dwelling, int level);
+	void showShipyardDialog(const IShipyard *obj); //obj may be town or shipyard; 
 	void showBlockingDialog(const std::string &text, const std::vector<Component> &components, ui32 askID, int soundID, bool selection, bool cancel); //Show a dialog, player must take decision. If selection then he has to choose between one of given components, if cancel he is allowed to not choose. After making choice, CCallback::selectionMade should be called with number of selected component (1 - n) or 0 for cancel (if allowed) and askID.
 	void showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, boost::function<void()> &onEnd);
 	void tileHidden(const std::set<int3> &pos); //called when given tiles become hidden under fog of war
 	void tileRevealed(const std::set<int3> &pos); //called when fog of war disappears from given tiles
+	void newObject(const CGObjectInstance * obj);
 	void yourTurn();
 	void availableCreaturesChanged(const CGTownInstance *town);
 	void heroBonusChanged(const CGHeroInstance *hero, const HeroBonus &bonus, bool gain);//if gain hero received bonus, else he lost it
@@ -206,7 +208,7 @@ public:
 	void handleMouseMotion(SDL_Event *sEvent);
 	void init(ICallback * CB);
 	int3 repairScreenPos(int3 pos); //returns position closest to pos we can center screen on
-	void showInfoDialog(const std::string &text, const std::vector<SComponent*> & components, int soundID);
+	void showInfoDialog(const std::string &text, const std::vector<SComponent*> & components = std::vector<SComponent*>(), int soundID = 0);
 	void showYesNoDialog(const std::string &text, const std::vector<SComponent*> & components, CFunctionList<void()> onYes, CFunctionList<void()> onNo, bool DelComps); //deactivateCur - whether current main interface should be deactivated; delComps - if components will be deleted on window close
 	bool moveHero(const CGHeroInstance *h, CPath path);
 

+ 63 - 0
client/GUIClasses.cpp

@@ -3976,4 +3976,67 @@ CExchangeWindow::~CExchangeWindow() //d-tor
 	}
 }
 
+void CShipyardWindow::activate()
+{
+	build->activate();
+	quit->activate();
+}
+
+void CShipyardWindow::deactivate()
+{
+	build->deactivate();
+	quit->deactivate();
+}
+
+void CShipyardWindow::show( SDL_Surface * to )
+{
+	blitAt(bg,pos,to);
+	CSDL_Ext::blit8bppAlphaTo24bpp(graphics->boatAnims[1]->ourImages[21 + frame++/8%8].bitmap, NULL, to, &genRect(64, 96, pos.x+110, pos.y+85));
+	build->show(to);
+	quit->show(to);
+}
+
+CShipyardWindow::~CShipyardWindow()
+{
+	delete build;
+	delete quit;
+}
 
+CShipyardWindow::CShipyardWindow(const std::vector<si32> &cost, int state, const boost::function<void()> &onBuy)
+{
+	frame = 0;
+	SDL_Surface * bgtemp; //loaded as 8bpp surface
+	bgtemp = BitmapHandler::loadBitmap("TPSHIP.bmp");
+	pos.x = screen->w/2 - bgtemp->w/2;
+	pos.y = screen->h/2 - bgtemp->h/2;
+	pos.w = bgtemp->w;
+	pos.h = bgtemp->h;
+	SDL_SetColorKey(bgtemp,SDL_SRCCOLORKEY,SDL_MapRGB(bgtemp->format,0,255,255));
+	graphics->blueToPlayersAdv(bgtemp, LOCPLINT->playerID);
+	bg = SDL_ConvertSurface(bgtemp, screen->format, screen->flags); //to 24 bpp
+	SDL_FreeSurface(bgtemp);
+
+	bgtemp = BitmapHandler::loadBitmap("TPSHIPBK.bmp");
+	blitAt(bgtemp, 100, 69, bg);
+	SDL_FreeSurface(bgtemp);
+
+	bool affordable = true;
+	for(int i = 0; i < cost.size(); i++)
+	{
+		if(cost[i] > LOCPLINT->cb->getResourceAmount(i))
+		{
+			affordable = false;
+			break;
+		}
+	}
+
+	quit = new AdventureMapButton(CGI->generaltexth->allTexts[599], "", boost::bind(&CPlayerInterface::popIntTotally, LOCPLINT, this), pos.x+224, pos.y+312, "ICANCEL.DEF", SDLK_RETURN);
+	build = new AdventureMapButton(CGI->generaltexth->allTexts[598], "", boost::bind(&CPlayerInterface::popIntTotally, LOCPLINT, this), pos.x+42, pos.y+312, "IBY6432.DEF", SDLK_RETURN);
+	build->callback += onBuy;
+
+	if(!affordable)
+		build->block(true);
+
+	printAtMiddle(CGI->generaltexth->jktexts[15], 165, 26, GEOR13, zwykly, bg); //Resource cost:
+	printAtMiddle(CGI->generaltexth->jktexts[14], 165, 218, GEOR16, tytulowy, bg); //Build A New Ship
+}

+ 15 - 0
client/GUIClasses.h

@@ -700,5 +700,20 @@ public:
 	~CExchangeWindow(); //d-tor
 };
 
+class CShipyardWindow : public CIntObject, public IShowActivable
+{
+public:
+	CStatusBar *bar;
+	SDL_Surface *bg; //background
+	AdventureMapButton *build, *quit;
+
+	unsigned char frame; //frame of the boat animation
+
+	void activate();
+	void deactivate();
+	void show(SDL_Surface * to);
+	CShipyardWindow(const std::vector<si32> &cost, int state, const boost::function<void()> &onBuy);
+	~CShipyardWindow();
+};
 
 #endif //__GUICLASSES_H__

+ 4 - 0
client/Graphics.cpp

@@ -16,6 +16,7 @@
 #include "../lib/VCMI_Lib.h"
 #include "../CCallback.h"
 #include "../hch/CTownHandler.h"
+#include "../hch/CDefObjInfoHandler.h"
 using namespace boost::assign;
 using namespace CSDL_Ext;
 #ifdef min
@@ -360,6 +361,9 @@ void Graphics::loadHeroAnims()
 	loadHeroAnim("AB01_.DEF", rotations, &Graphics::boatAnims);
 	loadHeroAnim("AB02_.DEF", rotations, &Graphics::boatAnims);
 	loadHeroAnim("AB03_.DEF", rotations, &Graphics::boatAnims);
+	VLC->dobjinfo->gobjs[8][0]->handler = boatAnims[0];
+	VLC->dobjinfo->gobjs[8][1]->handler = boatAnims[1];
+	VLC->dobjinfo->gobjs[8][2]->handler = boatAnims[2];
 }
 
 void Graphics::loadHeroAnim( const std::string &name, const std::vector<std::pair<int,int> > &rotations, std::vector<CDefEssential *> Graphics::*dst )

+ 22 - 0
client/NetPacksClient.cpp

@@ -488,6 +488,28 @@ void OpenWindow::applyCl(CClient *cl)
 			const CGDwelling *dw = dynamic_cast<const CGDwelling*>(cl->getObj(id1));
 			INTERFACE_CALL_IF_PRESENT(dw->tempOwner,showRecruitmentDialog, dw, 0);
 		}
+		break;
+	case SHIPYARD_WINDOW:
+		{
+			const IShipyard *sy = IShipyard::castFrom(cl->getObj(id1));
+			INTERFACE_CALL_IF_PRESENT(sy->o->tempOwner, showShipyardDialog, sy);
+		}
+		break;
 	}
 
 }
+
+void NewObject::applyCl(CClient *cl)
+{
+	const CGObjectInstance *obj = cl->getObj(id);
+	//notify interfaces about move
+	for(std::map<ui8, CGameInterface*>::iterator i=cl->playerint.begin();i!=cl->playerint.end();i++)
+	{
+		//TODO: check if any covered tile is visible
+		if(i->first >= PLAYER_LIMIT) continue;
+		if(GS(cl)->players[i->first].fogOfWarMap[obj->pos.x][obj->pos.y][obj->pos.z])
+		{
+			i->second->newObject(obj);
+		}
+	}
+}

+ 131 - 0
hch/CObjectHandler.cpp

@@ -9,6 +9,7 @@
 #include "CSpellHandler.h"
 #include <boost/bind.hpp>
 #include <boost/algorithm/string/replace.hpp>
+#include <boost/assign/std/vector.hpp> 
 #include <boost/lexical_cast.hpp>
 #include <boost/random/linear_congruential.hpp>
 #include "CTownHandler.h"
@@ -20,6 +21,7 @@
 #include "../lib/NetPacks.h"
 #include "../StartInfo.h"
 #include "../lib/map.h"
+using namespace boost::assign;
 
 /*
  * CObjectHandler.cpp, part of VCMI engine
@@ -291,6 +293,10 @@ void CGObjectInstance::giveDummyBonus(int heroID, ui8 duration) const
 	cb->giveHeroBonus(&gbonus);
 }
 
+void CGObjectInstance::onHeroVisit( const CGHeroInstance * h ) const
+{
+}
+
 static int lowestSpeed(const CGHeroInstance * chi)
 {
 	if(!chi->army.slots.size())
@@ -1222,6 +1228,7 @@ bool CGTownInstance::hasCapitol() const
 	return (builtBuildings.find(13))!=builtBuildings.end();
 }
 CGTownInstance::CGTownInstance()
+	:IShipyard(this)
 {
 	builded=-1;
 	destroyed=-1;
@@ -1283,6 +1290,11 @@ int3 CGTownInstance::getSightCenter() const
 	return pos - int3(2,0,0);
 }
 
+void CGTownInstance::getOutOffsets( std::vector<int3> &offsets ) const
+{
+	offsets += int3(-1,3,0), int3(-3,3,0);
+}
+
 void CGVisitableOPH::onHeroVisit( const CGHeroInstance * h ) const
 {
 	if(visitors.find(h->id)==visitors.end())
@@ -2498,6 +2510,7 @@ void CGBonusingObject::onHeroVisit( const CGHeroInstance * h ) const
 	{
 	case 11: //buoy
 		messageID = 21;
+		sound = soundBase::MORALE;
 		gbonus.bonus.type = HeroBonus::MORALE;
 		gbonus.bonus.val = +1;
 		gbonus.bdescr <<  std::pair<ui8,ui32>(6,94);
@@ -3412,4 +3425,122 @@ 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;
+//	getOutOffsets(offsets);
+//
+//	TerrainTile *tile;
+//	for(int i = 0; i < offsets.size(); i++)
+//		if((tile = IObjectInterface::cb->getTile(o->pos + offsets[i]))  &&  tile->tertype == TerrainTile::water) //tile is in the map and is water
+//			return true;
+//	return false;
+//}
+
+int3 IShipyard::bestLocation() const
+{
+	std::vector<int3> offsets;
+	getOutOffsets(offsets);
+
+	TerrainTile *tile;
+	for(int i = 0; i < offsets.size(); i++)
+		if((tile = IObjectInterface::cb->getTile(o->pos + offsets[i]))  &&  tile->tertype == TerrainTile::water) //tile is in the map and is water
+			return o->pos + offsets[i];
+	return int3(-1,-1,-1);
+}
+
+IShipyard::IShipyard(const CGObjectInstance *O) 
+	: o(O)
+{
+}
+
+int IShipyard::state() const
+{
+	int3 tile = bestLocation();
+	TerrainTile *t = IObjectInterface::cb->getTile(tile);
+	if(!t)
+		return 3; //no water
+	else if(!t->blockingObjects.size())
+		return 0; //OK
+	else if(t->blockingObjects.front()->ID == 8)
+		return 1; //blocked with boat
+	else
+		return 2; //blocked
+}
+
+IShipyard * IShipyard::castFrom( CGObjectInstance *obj )
+{
+	if(obj->ID == TOWNI_TYPE)
+	{
+		return static_cast<CGTownInstance*>(obj);
+	}
+	else if(obj->ID == 87)
+	{
+		return static_cast<CGShipyard*>(obj);
+	}
+	else
+	{
+		tlog1 << "Cannot cast to IShipyar object with ID " << obj->ID << std::endl;
+		return NULL;
+	}
+}
+
+const IShipyard * IShipyard::castFrom( const CGObjectInstance *obj )
+{
+	return castFrom(const_cast<CGObjectInstance*>(obj));
+}
+
+CGShipyard::CGShipyard()
+	:IShipyard(this)
+{
+}
+
+void CGShipyard::getOutOffsets( std::vector<int3> &offsets ) const
+{
+	offsets += int3(1,0,0), int3(-3,0,0), int3(1,1,0), int3(-3,1,0), int3(1,-1,0), int3(-3,-1,0), 
+		int3(-2,-1,0), int3(0,-1,0), int3(-1,-1,0), int3(-2,1,0), int3(0,1,0), int3(-1,1,0);
+}
+
+void CGShipyard::onHeroVisit( const CGHeroInstance * h ) const
+{
+	if(tempOwner != h->tempOwner)
+		cb->setOwner(id, h->tempOwner);
+
+	int s = state();
+	if(s)
+	{
+		InfoWindow iw;
+		iw.player = tempOwner;
+		switch(s)
+		{
+		case 1: 
+			iw.text.addTxt(MetaString::GENERAL_TXT, 51);
+			break;
+		case 2:
+			iw.text.addTxt(MetaString::ADVOB_TXT, 189);
+			break;
+		case 3:
+			tlog1 << "Shipyard without water!!! " << pos << "\t" << id << std::endl;
+			return;
+		}
+
+		cb->showInfoDialog(&iw);
+	}
+	else
+	{
+		OpenWindow ow;
+		ow.id1 = id;
+		ow.id2 = h->id;
+		ow.window = OpenWindow::SHIPYARD_WINDOW;
+		cb->sendAndApply(&ow);
+	}
 }

+ 26 - 2
hch/CObjectHandler.h

@@ -104,6 +104,22 @@ public:
 	virtual void setProperty(ui8 what, ui32 val);//synchr
 };
 
+class DLL_EXPORT IShipyard
+{
+public:
+	const CGObjectInstance *o;
+
+	IShipyard(const CGObjectInstance *O);
+	void getBoatCost(std::vector<si32> &cost) const;
+	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
+
+	static const IShipyard *castFrom(const CGObjectInstance *obj);
+	static IShipyard *castFrom(CGObjectInstance *obj);
+};
+
 class DLL_EXPORT CGObjectInstance : protected IObjectInterface
 {
 protected:
@@ -139,6 +155,7 @@ public:
 	virtual const std::string & getHoverText() const;
 	//////////////////////////////////////////////////////////////////////////
 	void initObj();
+	void onHeroVisit(const CGHeroInstance * h) const;
 	void setProperty(ui8 what, ui32 val);//synchr
 	virtual void setPropertyDer(ui8 what, ui32 val);//synchr
 
@@ -309,7 +326,7 @@ public:
 	void wantsFight(const CGHeroInstance *h, ui32 answer) const;
 };
 
-class DLL_EXPORT CGTownInstance : public CGDwelling
+class DLL_EXPORT CGTownInstance : public CGDwelling, public IShipyard
 {
 public:
 	CTown * town;
@@ -357,6 +374,7 @@ public:
 
 	int3 getSightCenter() const; //"center" tile from which the sight distance is calculated
 	int getSightRadious() const; //returns sight distance
+	void getOutOffsets(std::vector<int3> &offsets) const; //offsets to obj pos when we boat can be placed
 
 	//////////////////////////////////////////////////////////////////////////
 
@@ -777,7 +795,13 @@ public:
 	}
 };
 
-
+class CGShipyard : public CGObjectInstance, public IShipyard
+{
+public:
+	void getOutOffsets(std::vector<int3> &offsets) const; //offsets to obj pos when we boat can be placed
+	CGShipyard();
+	void onHeroVisit(const CGHeroInstance * h) const;
+};
 
 class DLL_EXPORT CObjectHandler
 {

+ 7 - 0
lib/IGameCallback.cpp

@@ -145,3 +145,10 @@ void IGameCallback::getAllowed(std::vector<CArtifact*> &out, int flags)
 	if(flags & CArtifact::ART_RELIC)
 		getAllowedArts(out,&CArtHandler::relics);
 }
+
+TerrainTile * IGameCallback::getTile( int3 pos )
+{
+	if(!gs->map->isInTheMap(pos))
+		return NULL;
+	return &gs->map->getTile(pos);
+}

+ 2 - 0
lib/IGameCallback.h

@@ -32,6 +32,7 @@ struct CPackForClient;
 class CArtHandler;
 class CArtifact;
 class CArmedInstance;
+struct TerrainTile;
 
 class DLL_EXPORT IGameCallback
 {
@@ -55,6 +56,7 @@ public:
 	virtual bool isAllowed(int type, int id); //type: 0 - spell; 1- artifact
 	virtual void getAllowedArts(std::vector<CArtifact*> &out, std::vector<CArtifact*> CArtHandler::*arts);
 	virtual void getAllowed(std::vector<CArtifact*> &out, int flags); //flags: bitfield uses EartClass
+	virtual TerrainTile * getTile(int3 pos);
 
 	//do sth
 	virtual void changeSpells(int hid, bool give, const std::set<ui32> &spells)=0;

+ 31 - 1
lib/NetPacks.h

@@ -518,7 +518,7 @@ struct OpenWindow : public CPackForClient //517
 	OpenWindow(){type = 517;};
 	void applyCl(CClient *cl);
 
-	enum EWindow {EXCHANGE_WINDOW, RECRUITMENT_FIRST, RECRUITMENT_ALL};
+	enum EWindow {EXCHANGE_WINDOW, RECRUITMENT_FIRST, RECRUITMENT_ALL, SHIPYARD_WINDOW};
 	ui8 window;
 	ui32 id1, id2;
 
@@ -528,6 +528,23 @@ struct OpenWindow : public CPackForClient //517
 	}
 };
 
+struct NewObject  : public CPackForClient //518
+{
+	NewObject(){type = 518;};
+	void applyCl(CClient *cl);
+	DLL_EXPORT void applyGs(CGameState *gs);
+
+	ui32 ID, subID;
+	int3 pos;
+
+	int id; //used internally
+
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & ID & subID & pos;
+	}
+};
+
 struct NewTurn : public CPackForClient //101
 {
 	DLL_EXPORT void applyGs(CGameState *gs);
@@ -1118,6 +1135,19 @@ struct HireHero : public CPackForServer
 	}
 };
 
+struct BuildBoat : public CPackForServer 
+{
+	BuildBoat(){};
+	si32 objid; //where player wants to buy a boat
+
+	bool applyGh(CGameHandler *gh);
+	template <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & objid;
+	}
+
+};
+
 struct QueryReply : public CPackForServer
 {
 	QueryReply(){};

+ 26 - 0
lib/NetPacksLib.cpp

@@ -490,6 +490,32 @@ DLL_EXPORT void GiveHero::applyGs( CGameState *gs )
 	h->inTownGarrison = false;
 }
 
+DLL_EXPORT void NewObject::applyGs( CGameState *gs )
+{
+	
+	CGObjectInstance *o = NULL;
+	switch(ID)
+	{
+	case 8:
+		o = new CGBoat();
+		break;
+	default:
+		o = new CGObjectInstance();
+		break;
+	}
+	o->ID = ID;
+	o->subID = subID;
+	o->pos = pos;
+	o->defInfo = VLC->dobjinfo->gobjs[ID][subID];
+	id = o->id = gs->map->objects.size();
+	o->hoverName = VLC->generaltexth->names[ID];
+
+	gs->map->objects.push_back(o);
+	gs->map->addBlockVisTiles(o);
+	o->initObj();
+	assert(o->defInfo);
+}
+
 DLL_EXPORT void NewTurn::applyGs( CGameState *gs )
 {
 	gs->day = day;

+ 3 - 0
lib/RegisterTypes.cpp

@@ -45,6 +45,7 @@ void registerTypes1(Serializer &s)
 	s.template registerType<CGBoat>();
 	s.template registerType<CGSirens>();
 	s.template registerType<CGOnceVisitable>();
+	s.template registerType<CGShipyard>();
 	s.template registerType<CGObjectInstance>();
 }
 
@@ -96,6 +97,7 @@ void registerTypes2(Serializer &s)
 	s.template registerType<StacksInjured>();
 	s.template registerType<ShowInInfobox>();
 	s.template registerType<OpenWindow>();
+	s.template registerType<NewObject>();
 
 	s.template registerType<SaveGame>();
 	s.template registerType<SetSelection>();
@@ -120,6 +122,7 @@ void registerTypes3(Serializer &s)
 	s.template registerType<TradeOnMarketplace>();
 	s.template registerType<SetFormation>();
 	s.template registerType<HireHero>();
+	s.template registerType<BuildBoat>();
 	s.template registerType<QueryReply>();
 	s.template registerType<MakeAction>();
 	s.template registerType<MakeCustomAction>();

+ 18 - 2
lib/map.cpp

@@ -1688,7 +1688,6 @@ void Mapa::readObjects( unsigned char * bufor, int &i)
 				break;
 			}
 		case 42: //lighthouse
-		case 87: //shipyard
 		case 220://mine (?)
 			{
 				nobj = new CGObjectInstance();
@@ -1881,6 +1880,12 @@ void Mapa::readObjects( unsigned char * bufor, int &i)
 				nobj = new CGSirens();
 				break;
 			}
+		case 87: //Shipyard
+			{
+				nobj = new CGShipyard();
+				nobj->setOwner(readNormalNr(bufor,i)); i+=4;
+				break;
+			}
 		case 214: //hero placeholder
 			{
 				i+=3; //TODO: handle it more properly
@@ -1945,7 +1950,7 @@ void Mapa::readEvents( unsigned char * bufor, int &i )
 	}
 }
 
-bool Mapa::isInTheMap( int3 pos )
+bool Mapa::isInTheMap( int3 pos ) const
 {
 	if(pos.x<0 || pos.y<0 || pos.z<0 || pos.x >= width || pos.y >= height || pos.z > twoLevel)
 		return false;
@@ -2034,6 +2039,17 @@ TerrainTile & Mapa::getTile( int3 tile )
 {
 	return terrain[tile.x][tile.y][tile.z];
 }
+
+const TerrainTile & Mapa::getTile( int3 tile ) const
+{
+	return terrain[tile.x][tile.y][tile.z];
+}
+
+bool Mapa::isWaterTile( int3 pos ) const
+{
+	return isInTheMap(pos) && getTile(pos).tertype == TerrainTile::water;
+}
+
 void CMapInfo::countPlayers()
 {
 	playerAmnt=humenPlayers=0;

+ 3 - 1
lib/map.h

@@ -335,8 +335,10 @@ struct DLL_EXPORT Mapa : public CMapHeader
 	Mapa();
 	~Mapa();
 	TerrainTile &getTile(int3 tile);
+	const TerrainTile &getTile(int3 tile) const;
 	CGHeroInstance * getHero(int ID, int mode=0);
-	bool isInTheMap(int3 pos);
+	bool isInTheMap(int3 pos) const;
+	bool isWaterTile(int3 pos) const; //out-of-pos safe
 	template <typename Handler> void serialize(Handler &h, const int formatVersion)
 	{
 		h & static_cast<CMapHeader&>(*this);

+ 55 - 42
mapHandler.cpp

@@ -720,13 +720,13 @@ void CMapHandler::terrainRect(int3 top_tile, unsigned char anim, std::vector< st
 
 			for(int h=0; h < objects.size(); ++h)
 			{
+				const CGObjectInstance *obj = objects[h].first;
+				ui8 color = obj->tempOwner;
 				//checking if object has non-empty graphic on this tile
-				if(!objects[h].first->coveringAt(objects[h].first->pos.x - (top_tile.x + bx), top_tile.y + by - objects[h].first->pos.y + 5))
+				if(!obj->coveringAt(obj->pos.x - (top_tile.x + bx), top_tile.y + by - obj->pos.y + 5))
 					continue;
 
-				//printing object
 				SDL_Rect sr;
-
 				sr.x = srx;
 				sr.y = sry;
 				sr.w = sr.h = 32;
@@ -735,73 +735,87 @@ void CMapHandler::terrainRect(int3 top_tile, unsigned char anim, std::vector< st
 				pp.h = sr.h;
 				pp.w = sr.w;
 
-				const CGHeroInstance * themp = (objects[h].first->ID != HEROI_TYPE  
+				const CGHeroInstance * themp = (obj->ID != HEROI_TYPE  
 					? NULL  
-					: static_cast<const CGHeroInstance*>(objects[h].first));
+					: static_cast<const CGHeroInstance*>(obj));
 
-				if(themp && themp->moveDir) //it's hero
+				//print hero / boat and flag
+				if(themp && themp->moveDir && themp->type  ||  obj->ID == 8) //it's hero or boat
 				{
-					int imgVal = 8;
-					SDL_Surface * tb;
-					if(themp->type==NULL)
-						continue;
-
-					//pick graphics of hero (or boat if hero is sailing)
-					std::vector<Cimage> & iv = (themp->boat) 
-												? graphics->boatAnims[themp->boat->subID]->ourImages
-												: graphics->heroAnims[themp->type->heroType]->ourImages;
-
-					//pick appropriate flag set
+					const int IMGVAL = 8; //frames per group of movement animation
+					ui8 dir;
+					std::vector<Cimage> * iv = NULL;
 					std::vector<CDefEssential *> Graphics::*flg = NULL;
-					if(themp->boat)
+					SDL_Surface * tb; //surface to blitted
+					
+					if(themp) //hero
 					{
-						switch (themp->boat->subID)
+						dir = themp->moveDir;
+
+						//pick graphics of hero (or boat if hero is sailing)
+						iv = (themp->boat) 
+							? &graphics->boatAnims[themp->boat->subID]->ourImages
+							: &graphics->heroAnims[themp->type->heroType]->ourImages;
+
+						//pick appropriate flag set
+						if(themp->boat)
 						{
-						case 0: flg = &Graphics::flags1; break;
-						case 1: flg = &Graphics::flags2; break;
-						case 2: flg = &Graphics::flags3; break;
-						default: tlog1 << "Not supported boat subtype: " << themp->boat->subID << std::endl;
+							switch (themp->boat->subID)
+							{
+							case 0: flg = &Graphics::flags1; break;
+							case 1: flg = &Graphics::flags2; break;
+							case 2: flg = &Graphics::flags3; break;
+							default: tlog1 << "Not supported boat subtype: " << themp->boat->subID << std::endl;
+							}
+						}
+						else
+						{
+							flg = &Graphics::flags4;
 						}
 					}
-					else
+					else //boat
 					{
-						flg = &Graphics::flags4;
+						const CGBoat *boat = static_cast<const CGBoat*>(obj);
+						dir = boat->direction;
+						iv = &graphics->boatAnims[boat->subID]->ourImages;
 					}
 
-					//print hero / boat and flag
-					if(!themp->isStanding) //hero is moving
+
+					if(themp && !themp->isStanding) //hero is moving
 					{
 						size_t gg;
-						for(gg=0; gg<iv.size(); ++gg)
+						for(gg=0; gg<iv->size(); ++gg)
 						{
-							if(iv[gg].groupNumber==getHeroFrameNum(themp->moveDir, !themp->isStanding))
+							if((*iv)[gg].groupNumber==getHeroFrameNum(dir, true))
 							{
-								tb = iv[gg+heroAnim%imgVal].bitmap;
+								tb = (*iv)[gg+heroAnim%IMGVAL].bitmap;
 								break;
 							}
 						}
 						CSDL_Ext::blit8bppAlphaTo24bpp(tb,&pp,extSurf,&sr);
 
 						//printing flag
-						pp.y+=imgVal*2-32;
+						pp.y+=IMGVAL*2-32;
 						sr.y-=16;
-						SDL_BlitSurface((graphics->*flg)[themp->getOwner()]->ourImages[gg+heroAnim%imgVal+35].bitmap, &pp, extSurf, &sr);
+						SDL_BlitSurface((graphics->*flg)[color]->ourImages[gg+heroAnim%IMGVAL+35].bitmap, &pp, extSurf, &sr);
 					}
-					else //hero stands still
+					else //hero / boat stands still
 					{
 						size_t gg;
-						for(gg=0; gg < iv.size(); ++gg)
+						for(gg=0; gg < iv->size(); ++gg)
 						{
-							if(iv[gg].groupNumber==getHeroFrameNum(themp->moveDir, !themp->isStanding))
+							if((*iv)[gg].groupNumber==getHeroFrameNum(dir, false))
 							{
-								tb = iv[gg].bitmap;
+								tb = (*iv)[gg].bitmap;
 								break;
 							}
 						}
 						CSDL_Ext::blit8bppAlphaTo24bpp(tb,&pp,extSurf,&sr);
 
 						//printing flag
-						if(themp->pos.x==top_tile.x+bx && themp->pos.y==top_tile.y+by)
+						if(flg  
+							&&  obj->pos.x == top_tile.x + bx  
+							&&  obj->pos.y == top_tile.y + by)
 						{
 							SDL_Rect bufr = sr;
 							bufr.x-=2*32;
@@ -809,19 +823,18 @@ void CMapHandler::terrainRect(int3 top_tile, unsigned char anim, std::vector< st
 							bufr.h = 64;
 							bufr.w = 96;
 							if(bufr.x-extRect->x>-64)
-								SDL_BlitSurface((graphics->*flg)[themp->getOwner()]->ourImages[ getHeroFrameNum(themp->moveDir, !themp->isStanding) *8+(heroAnim/4)%imgVal].bitmap, NULL, extSurf, &bufr);
+								SDL_BlitSurface((graphics->*flg)[color]->ourImages[getHeroFrameNum(dir, false) *8+(heroAnim/4)%IMGVAL].bitmap, NULL, extSurf, &bufr);
 						}
 					}
 				}
-				else //blit object
+				else //blit normal object
 				{
-					const CGObjectInstance *obj = objects[h].first;
 					const std::vector<Cimage> &ourImages = obj->defInfo->handler->ourImages;
 					SDL_Surface *bitmap = ourImages[(anim+obj->animPhaseShift)%ourImages.size()].bitmap;
 
 					//setting appropriate flag color
-					if(obj->tempOwner<8 || obj->tempOwner==255)
-						CSDL_Ext::setPlayerColor(bitmap, obj->tempOwner);
+					if(color < 8 || color==255)
+						CSDL_Ext::setPlayerColor(bitmap, color);
 					
 					CSDL_Ext::blit8bppAlphaTo24bpp(bitmap,&pp,extSurf,&sr);
 				}

+ 47 - 1
server/CGameHandler.cpp

@@ -662,7 +662,7 @@ void CGameHandler::newTurn()
 		{
 			NewTurn::Hero hth;
 			hth.id = h->id;
-			hth.move = h->maxMovePoints(true); //TODO: check if hero is really on the land
+			hth.move = h->maxMovePoints(gs->map->getTile(h->getPosition(false)).tertype != TerrainTile::water);
 
 			if(h->visitedTown && vstd::contains(h->visitedTown->builtBuildings,0)) //if hero starts turn in town with mage guild
 				hth.mana = h->manaLimit(); //restore all mana
@@ -2872,3 +2872,49 @@ void CGameHandler::objectVisited( const CGObjectInstance * obj, const CGHeroInst
 {
 	obj->onHeroVisit(h);
 }
+
+bool CGameHandler::buildBoat( ui32 objid )
+{
+	const IShipyard *obj = IShipyard::castFrom(getObj(objid));
+
+	if(obj->state())
+	{
+		complain("Cannot build boat in this shipyard!");
+		return false;
+	}
+	else if(obj->o->ID == TOWNI_TYPE
+		&& !vstd::contains((static_cast<const CGTownInstance*>(obj))->builtBuildings,6))
+	{
+		complain("Cannot build boat in the town - no shipyard!");
+		return false;
+	}
+
+	//TODO use "real" cost via obj->getBoatCost
+	if(getResource(obj->o->tempOwner, 6) < 1000  ||  getResource(obj->o->tempOwner, 0) < 10)
+	{
+		complain("Not enough resources to build a boat!");
+		return false;
+	}
+
+	int3 tile = obj->bestLocation();
+	if(!gs->map->isInTheMap(tile))
+	{
+		complain("Cannot find appropriate tile for a boat!");
+		return false;
+	}
+
+	//take boat cost
+	SetResources sr;
+	sr.player = obj->o->tempOwner;
+	sr.res = gs->getPlayer(obj->o->tempOwner)->resources;
+	sr.res[0] -= 10;
+	sr.res[6] -= 1000;
+	sendAndApply(&sr);
+
+	//create boat
+	NewObject no;
+	no.ID = 8;
+	no.subID = 1;
+	no.pos = tile + int3(1,0,0);
+	sendAndApply(&no);
+}

+ 1 - 0
server/CGameHandler.h

@@ -144,6 +144,7 @@ public:
 	bool makeCustomAction(BattleAction &ba);
 	bool queryReply( ui32 qid, ui32 answer );
 	bool hireHero( ui32 tid, ui8 hid );
+	bool buildBoat( ui32 objid );
 	bool setFormation( si32 hid, ui8 formation );
 	bool tradeResources( ui32 val, ui8 player, ui32 id1, ui32 id2 );
 	bool buyArtifact( ui32 hid, si32 aid );

+ 6 - 0
server/NetPacksServer.cpp

@@ -122,6 +122,12 @@ bool HireHero::applyGh( CGameHandler *gh )
 	return gh->hireHero(tid,hid);
 }
 
+bool BuildBoat::applyGh( CGameHandler *gh )
+{
+	ERROR_IF_NOT_OWNS(objid);
+	return gh->buildBoat(objid);
+}
+
 bool QueryReply::applyGh( CGameHandler *gh )
 {
 	//TODO - check if player matches the query