1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-20 20:23:03 +02:00
vcmi/client/Client.cpp
Michał W. Urbańczyk cab8f9e11d AI for neutral creatures and battles and player AIs can be different. StupidAI will handle neutrals by default.
Fixes for issues with deserialization and handling Tactics secondary skill.
2011-02-23 03:57:45 +00:00

676 lines
17 KiB
C++

#include "CMusicHandler.h"
#include "../lib/CCampaignHandler.h"
#include "../CCallback.h"
#include "../CConsoleHandler.h"
#include "CGameInfo.h"
#include "../lib/CGameState.h"
#include "CPlayerInterface.h"
#include "../StartInfo.h"
#include "../lib/BattleState.h"
#include "../lib/CArtHandler.h"
#include "../lib/CDefObjInfoHandler.h"
#include "../lib/CGeneralTextHandler.h"
#include "../lib/CHeroHandler.h"
#include "../lib/CTownHandler.h"
#include "../lib/CObjectHandler.h"
#include "../lib/CBuildingHandler.h"
#include "../lib/CSpellHandler.h"
#include "../lib/Connection.h"
#include "../lib/Interprocess.h"
#include "../lib/NetPacks.h"
#include "../lib/VCMI_Lib.h"
#include "../lib/map.h"
#include "mapHandler.h"
#include "CConfigHandler.h"
#include "Client.h"
#include "GUIBase.h"
#include <boost/bind.hpp>
#include <boost/foreach.hpp>
#include <boost/thread.hpp>
#include <boost/thread/shared_mutex.hpp>
#include <boost/lexical_cast.hpp>
#include <sstream>
#include "CPreGame.h"
#define NOT_LIB
#include "../lib/RegisterTypes.cpp"
#include "CBattleInterface.h"
extern std::string NAME;
namespace intpr = boost::interprocess;
/*
* Client.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
*
*/
template <typename T> class CApplyOnCL;
class CBaseForCLApply
{
public:
virtual void applyOnClAfter(CClient *cl, void *pack) const =0;
virtual void applyOnClBefore(CClient *cl, void *pack) const =0;
virtual ~CBaseForCLApply(){}
template<typename U> static CBaseForCLApply *getApplier(const U * t=NULL)
{
return new CApplyOnCL<U>;
}
};
template <typename T> class CApplyOnCL : public CBaseForCLApply
{
public:
void applyOnClAfter(CClient *cl, void *pack) const
{
T *ptr = static_cast<T*>(pack);
ptr->applyCl(cl);
}
void applyOnClBefore(CClient *cl, void *pack) const
{
T *ptr = static_cast<T*>(pack);
ptr->applyFirstCl(cl);
}
};
static CApplier<CBaseForCLApply> *applier = NULL;
void CClient::init()
{
hotSeat = false;
connectionHandler = NULL;
pathInfo = NULL;
applier = new CApplier<CBaseForCLApply>;
registerTypes2(*applier);
IObjectInterface::cb = this;
serv = NULL;
gs = NULL;
cb = NULL;
terminate = false;
}
CClient::CClient(void)
:waitingRequest(false)
{
init();
}
CClient::CClient(CConnection *con, StartInfo *si)
:waitingRequest(false)
{
init();
newGame(con,si);
}
CClient::~CClient(void)
{
delete pathInfo;
delete applier;
}
void CClient::waitForMoveAndSend(int color)
{
try
{
assert(vstd::contains(battleints, color));
BattleAction ba = battleints[color]->activeStack(gs->curB->getStack(gs->curB->activeStack, false));
*serv << &MakeAction(ba);
return;
}HANDLE_EXCEPTION
tlog1 << "We should not be here!" << std::endl;
}
void CClient::run()
{
try
{
CPack *pack = NULL;
while(!terminate)
{
pack = serv->retreivePack(); //get the package from the server
if (terminate)
{
delete pack;
pack = NULL;
break;
}
handlePack(pack);
pack = NULL;
}
}
catch (const std::exception& e)
{
tlog3 << "Lost connection to server, ending listening thread!\n";
tlog1 << e.what() << std::endl;
if(!terminate) //rethrow (-> boom!) only if closing connection was unexpected
{
tlog1 << "Something wrong, lost connection while game is still ongoing...\n";
throw;
}
}
}
void CClient::save(const std::string & fname)
{
if(gs->curB)
{
tlog1 << "Game cannot be saved during battle!\n";
return;
}
*serv << &SaveGame(fname);
}
#include <fstream>
void initVillagesCapitols(Mapa * map)
{
std::ifstream ifs(DATA_DIR "/config/townsDefs.txt");
int ccc;
ifs>>ccc;
for(int i=0;i<ccc*2;i++)
{
const CGDefInfo *n;
if(i<ccc)
{
n = CGI->state->villages[i];
map->defy.push_back(CGI->state->forts[i]);
}
else
n = CGI->state->capitols[i%ccc];
ifs >> const_cast<CGDefInfo*>(n)->name;
if(!n)
tlog1 << "*HUGE* Warning - missing town def for " << i << std::endl;
else
map->defy.push_back(const_cast<CGDefInfo*>(n));
}
}
void CClient::endGame( bool closeConnection /*= true*/ )
{
// Game is ending
// Tell the network thread to reach a stable state
GH.curInt = NULL;
LOCPLINT->terminate_cond.setn(true);
LOCPLINT->pim->lock();
tlog0 << "\n\nEnding current game!" << std::endl;
if(GH.topInt())
GH.topInt()->deactivate();
GH.listInt.clear();
GH.objsToBlit.clear();
GH.statusbar = NULL;
tlog0 << "Removed GUI." << std::endl;
delete CGI->mh;
const_cast<CGameInfo*>(CGI)->mh = NULL;
const_cast<CGameInfo*>(CGI)->state.dellNull();
tlog0 << "Deleted mapHandler and gameState." << std::endl;
CPlayerInterface * oldInt = LOCPLINT;
LOCPLINT = NULL;
oldInt->pim->unlock();
while (!playerint.empty())
{
CGameInterface *pint = playerint.begin()->second;
playerint.erase(playerint.begin());
delete pint;
}
BOOST_FOREACH(CCallback *cb, callbacks)
{
delete cb;
}
tlog0 << "Deleted playerInts." << std::endl;
if(closeConnection)
stopConnection();
tlog0 << "Client stopped." << std::endl;
}
void CClient::loadGame( const std::string & fname )
{
tlog0 <<"\n\nLoading procedure started!\n\n";
CServerHandler sh;
sh.startServer();
timeHandler tmh;
{
char sig[8];
CMapHeader dum;
const_cast<CGameInfo*>(CGI)->mh = new CMapHandler();
StartInfo *si;
CLoadFile lf(fname + ".vlgm1");
lf >> sig >> dum >> si;
tlog0 <<"Reading save signature: "<<tmh.getDif()<<std::endl;
lf >> *VLC;
const_cast<CGameInfo*>(CGI)->setFromLib();
tlog0 <<"Reading handlers: "<<tmh.getDif()<<std::endl;
lf >> gs;
tlog0 <<"Reading gamestate: "<<tmh.getDif()<<std::endl;
const_cast<CGameInfo*>(CGI)->state = gs;
const_cast<CGameInfo*>(CGI)->mh->map = gs->map;
pathInfo = new CPathsInfo(int3(gs->map->width, gs->map->height, gs->map->twoLevel+1));
CGI->mh->init();
initVillagesCapitols(gs->map);
tlog0 <<"Initing maphandler: "<<tmh.getDif()<<std::endl;
}
serv = sh.connectToServer();
serv->addStdVecItems(gs);
tmh.update();
ui8 pom8;
*serv << ui8(3) << ui8(1); //load game; one client
*serv << fname;
*serv >> pom8;
if(pom8)
throw "Server cannot open the savegame!";
else
tlog0 << "Server opened savegame properly.\n";
*serv << ui32(gs->scenarioOps->playerInfos.size()+1); //number of players + neutral
for(std::map<int, PlayerSettings>::iterator it = gs->scenarioOps->playerInfos.begin();
it != gs->scenarioOps->playerInfos.end(); ++it)
{
*serv << ui8(it->first); //players
}
*serv << ui8(255); // neutrals
tlog0 <<"Sent info to server: "<<tmh.getDif()<<std::endl;
{
CLoadFile lf(fname + ".vcgm1");
lf >> *this;
}
}
int CClient::getCurrentPlayer()
{
return gs->currentPlayer;
}
int CClient::getSelectedHero()
{
if(const CGHeroInstance *selHero = IGameCallback::getSelectedHero(getCurrentPlayer()))
return selHero->id;
else
return -1;
}
void CClient::newGame( CConnection *con, StartInfo *si )
{
enum {SINGLE, HOST, GUEST} networkMode = SINGLE;
std::set<ui8> myPlayers;
if (con == NULL)
{
CServerHandler sh;
serv = sh.connectToServer();
}
else
{
serv = con;
networkMode = (con->connectionID == 1) ? HOST : GUEST;
}
for(std::map<int, PlayerSettings>::iterator it =si->playerInfos.begin();
it != si->playerInfos.end(); ++it)
{
if(networkMode == SINGLE //single - one client has all player
|| networkMode != SINGLE && serv->connectionID == it->second.human //multi - client has only "its players"
|| networkMode == HOST && it->second.human == false) //multi - host has all AI players
{
myPlayers.insert(ui8(it->first)); //add player
}
}
if(networkMode != GUEST)
myPlayers.insert(255); //neutral
timeHandler tmh;
const_cast<CGameInfo*>(CGI)->state = new CGameState();
tlog0 <<"\tGamestate: "<<tmh.getDif()<<std::endl;
CConnection &c(*serv);
////////////////////////////////////////////////////
if(networkMode == SINGLE)
{
ui8 pom8;
c << ui8(2) << ui8(1); //new game; one client
c << *si;
c >> pom8;
if(pom8)
throw "Server cannot open the map!";
else
tlog0 << "Server opened map properly.\n";
}
c << myPlayers;
ui32 seed, sum;
c >> si >> sum >> seed;
tlog0 <<"\tSending/Getting info to/from the server: "<<tmh.getDif()<<std::endl;
tlog0 << "\tUsing random seed: "<<seed << std::endl;
gs = const_cast<CGameInfo*>(CGI)->state;
gs->scenarioOps = si;
gs->init(si, sum, seed);
tlog0 <<"Initializing GameState (together): "<<tmh.getDif()<<std::endl;
if(gs->map)
{
const_cast<CGameInfo*>(CGI)->mh = new CMapHandler();
CGI->mh->map = gs->map;
tlog0 <<"Creating mapHandler: "<<tmh.getDif()<<std::endl;
CGI->mh->init();
initVillagesCapitols(gs->map);
pathInfo = new CPathsInfo(int3(gs->map->width, gs->map->height, gs->map->twoLevel+1));
tlog0 <<"Initializing mapHandler (together): "<<tmh.getDif()<<std::endl;
}
int humanPlayers = 0;
for(std::map<int, PlayerSettings>::iterator it = gs->scenarioOps->playerInfos.begin();
it != gs->scenarioOps->playerInfos.end(); ++it)//initializing interfaces for players
{
ui8 color = it->first;
gs->currentPlayer = color;
if(!vstd::contains(myPlayers, color))
continue;
if(si->mode != StartInfo::DUEL)
{
CCallback *cb = new CCallback(gs,color,this);
if(!it->second.human)
{
playerint[color] = static_cast<CGameInterface*>(CAIHandler::getNewAI(conf.cc.defaultPlayerAI));
}
else
{
playerint[color] = new CPlayerInterface(color);
humanPlayers++;
}
battleints[color] = playerint[color];
playerint[color]->init(cb);
}
else
{
CBattleCallback * cbc = new CBattleCallback(gs, color, this);
battleints[color] = CAIHandler::getNewBattleAI("StupidAI");
battleints[color]->init(cbc);
}
}
if(si->mode == StartInfo::DUEL)
{
CPlayerInterface *p = new CPlayerInterface(-1);
p->observerInDuelMode = true;
battleints[254] = playerint[254] = p;
GH.curInt = p;
p->init(new CCallback(gs, -1, this));
battleStarted(gs->curB);
}
else
{
battleints[255] = CAIHandler::getNewBattleAI(conf.cc.defaultBattleAI);
battleints[255]->init(new CBattleCallback(gs, 255, this));
// playerint[255]->init(new CCallback(gs,255,this));
// battleints[255] = playerint[255];
}
serv->addStdVecItems(const_cast<CGameInfo*>(CGI)->state);
hotSeat = (humanPlayers > 1);
}
template <typename Handler>
void CClient::serialize( Handler &h, const int version )
{
h & hotSeat;
if(h.saving)
{
ui8 players = playerint.size();
h & players;
for(std::map<ui8,CGameInterface *>::iterator i = playerint.begin(); i != playerint.end(); i++)
{
h & i->first & i->second->dllName;
i->second->serialize(h,version);
}
}
else
{
ui8 players;
h & players;
for(int i=0; i < players; i++)
{
std::string dllname;
ui8 pid;
h & pid & dllname;
CGameInterface *nInt = NULL;
if(dllname.length())
{
if(pid == 255)
{
CBattleCallback * cbc = new CBattleCallback(gs, pid, this);
CBattleGameInterface *cbgi = CAIHandler::getNewBattleAI(dllname);
battleints[pid] = cbgi;
cbgi->init(cb);
//TODO? consider serialization
continue;
}
else
nInt = CAIHandler::getNewAI(dllname);
}
else
nInt = new CPlayerInterface(pid);
CCallback *callback = new CCallback(gs,pid,this);
callbacks.insert(callback);
battleints[pid] = playerint[pid] = nInt;
nInt->init(callback);
nInt->serialize(h, version);
}
}
}
void CClient::handlePack( CPack * pack )
{
CBaseForCLApply *apply = applier->apps[typeList.getTypeID(pack)]; //find the applier
if(apply)
{
apply->applyOnClBefore(this,pack);
tlog5 << "\tMade first apply on cl\n";
gs->apply(pack);
tlog5 << "\tApplied on gs\n";
apply->applyOnClAfter(this,pack);
tlog5 << "\tMade second apply on cl\n";
}
else
{
tlog1 << "Message cannot be applied, cannot find applier!\n";
}
delete pack;
}
void CClient::updatePaths()
{
const CGHeroInstance *h = getHero(getSelectedHero());
if (h)//if we have selected hero...
gs->calculatePaths(h, *pathInfo);
}
void CClient::finishCampaign( CCampaignState * camp )
{
}
void CClient::proposeNextMission( CCampaignState * camp )
{
GH.pushInt(new CBonusSelection(camp));
GH.curInt = CGP;
}
void CClient::stopConnection()
{
terminate = true;
if (serv) //request closing connection
{
tlog0 << "Connection has been requested to be closed.\n";
boost::unique_lock<boost::mutex>(*serv->wmx);
*serv << &CloseServer();
tlog0 << "Sent closing signal to the server\n";
}
if(connectionHandler)//end connection handler
{
if(connectionHandler->get_id() != boost::this_thread::get_id())
connectionHandler->join();
tlog0 << "Connection handler thread joined" << std::endl;
delete connectionHandler;
connectionHandler = NULL;
}
if (serv) //and delete connection
{
serv->close();
delete serv;
serv = NULL;
tlog3 << "Our socket has been closed." << std::endl;
}
}
void CClient::battleStarted(const BattleInfo * info)
{
CPlayerInterface * att, * def;
if(vstd::contains(playerint, info->sides[0]) && playerint[info->sides[0]]->human)
att = static_cast<CPlayerInterface*>( playerint[info->sides[0]] );
else
att = NULL;
if(vstd::contains(playerint, info->sides[1]) && playerint[info->sides[1]]->human)
def = static_cast<CPlayerInterface*>( playerint[info->sides[1]] );
else
def = NULL;
new CBattleInterface(info->belligerents[0], info->belligerents[1], info->heroes[0], info->heroes[1], Rect((conf.cc.resx - 800)/2, (conf.cc.resy - 600)/2, 800, 600), att, def);
if(vstd::contains(battleints,info->sides[0]))
battleints[info->sides[0]]->battleStart(info->belligerents[0], info->belligerents[1], info->tile, info->heroes[0], info->heroes[1], 0);
if(vstd::contains(battleints,info->sides[1]))
battleints[info->sides[1]]->battleStart(info->belligerents[0], info->belligerents[1], info->tile, info->heroes[0], info->heroes[1], 1);
if(vstd::contains(battleints,254))
battleints[254]->battleStart(info->belligerents[0], info->belligerents[1], info->tile, info->heroes[0], info->heroes[1], 1);
}
template void CClient::serialize( CISer<CLoadFile> &h, const int version );
template void CClient::serialize( COSer<CSaveFile> &h, const int version );
void CServerHandler::startServer()
{
th.update();
serverThread = new boost::thread(&CServerHandler::callServer, this); //runs server executable; //TODO: will it work on non-windows platforms?
if(verbose)
tlog0 << "Setting up thread calling server: " << th.getDif() << std::endl;
}
void CServerHandler::waitForServer()
{
if(!serverThread)
startServer();
th.update();
intpr::scoped_lock<intpr::interprocess_mutex> slock(shared->sr->mutex);
while(!shared->sr->ready)
{
shared->sr->cond.wait(slock);
}
if(verbose)
tlog0 << "Waiting for server: " << th.getDif() << std::endl;
}
CConnection * CServerHandler::connectToServer()
{
if(!shared->sr->ready)
waitForServer();
th.update();
CConnection *ret = justConnectToServer(conf.cc.server, port);
if(verbose)
tlog0<<"\tConnecting to the server: "<<th.getDif()<<std::endl;
return ret;
}
CServerHandler::CServerHandler(bool runServer /*= false*/)
{
serverThread = NULL;
shared = NULL;
port = boost::lexical_cast<std::string>(conf.cc.port);
boost::interprocess::shared_memory_object::remove("vcmi_memory"); //if the application has previously crashed, the memory may not have been removed. to avoid problems - try to destroy it
try
{
shared = new SharedMem();
} HANDLE_EXCEPTIONC(tlog1 << "Cannot open interprocess memory: ";)
}
CServerHandler::~CServerHandler()
{
delete shared;
delete serverThread; //detaches, not kills thread
}
void CServerHandler::callServer()
{
std::string comm = std::string(BIN_DIR PATH_SEPARATOR SERVER_NAME " ") + port + " > server_log.txt";
std::system(comm.c_str());
tlog0 << "Server finished\n";
}
CConnection * CServerHandler::justConnectToServer(const std::string &host, const std::string &port)
{
CConnection *ret = NULL;
while(!ret)
{
try
{
tlog0 << "Establishing connection...\n";
ret = new CConnection( host.size() ? host : conf.cc.server,
port.size() ? port : boost::lexical_cast<std::string>(conf.cc.port),
NAME);
}
catch(...)
{
tlog1 << "\nCannot establish connection! Retrying within 2 seconds" << std::endl;
SDL_Delay(2000);
}
}
return ret;
}