diff options
Diffstat (limited to 'libkdegames/kgame/kgame.cpp')
-rw-r--r-- | libkdegames/kgame/kgame.cpp | 1475 |
1 files changed, 1475 insertions, 0 deletions
diff --git a/libkdegames/kgame/kgame.cpp b/libkdegames/kgame/kgame.cpp new file mode 100644 index 00000000..101dbfcc --- /dev/null +++ b/libkdegames/kgame/kgame.cpp @@ -0,0 +1,1475 @@ +/* + This file is part of the KDE games library + Copyright (C) 2001 Martin Heni ([email protected]) + Copyright (C) 2001 Andreas Beckermann ([email protected]) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +/* + $Id$ +*/ + +#include "kgame.h" +#include "kgame.moc" +#include "kgamepropertyhandler.h" +#include "kgameproperty.h" +#include "kplayer.h" +#include "kgameio.h" +#include "kgameerror.h" +#include "kgamesequence.h" + +#include "kgamemessage.h" + +#include <unistd.h> +#include <stdio.h> +#include <assert.h> + +#include <qbuffer.h> +#include <qtimer.h> +#include <qptrqueue.h> +#include <qfile.h> + +#include <klocale.h> +#include <krandomsequence.h> +#include <kdebug.h> + +#define KGAME_LOAD_COOKIE 4210 + +// try to place as much as possible here +// many things are *not* possible here as KGame has to use some inline function +class KGamePrivate +{ +public: + KGamePrivate() + { + mUniquePlayerNumber = 0; + mPolicy=KGame::PolicyLocal; + mGameSequence = 0; + } + + int mUniquePlayerNumber; + QPtrQueue<KPlayer> mAddPlayerList;// this is a list of to-be-added players. See addPlayer() docu + KRandomSequence* mRandom; + KGame::GamePolicy mPolicy; + KGameSequence* mGameSequence; + + + KGamePropertyHandler* mProperties; + + // player lists + KGame::KGamePlayerList mPlayerList; + KGame::KGamePlayerList mInactivePlayerList; + + //KGamePropertys + KGamePropertyInt mMaxPlayer; + KGamePropertyUInt mMinPlayer; + KGamePropertyInt mGameStatus; // Game running? + QValueList<int> mInactiveIdList; + +}; + +// ------------------- GAME CLASS -------------------------- +KGame::KGame(int cookie,QObject* parent) : KGameNetwork(cookie,parent) +{ + kdDebug(11001) << k_funcinfo << " - " << this << ", sizeof(KGame)=" << sizeof(KGame) << endl; + d = new KGamePrivate; + + d->mProperties = new KGamePropertyHandler(this); + + d->mProperties->registerHandler(KGameMessage::IdGameProperty, + this,SLOT(sendProperty(int, QDataStream&, bool* )), + SLOT(emitSignal(KGamePropertyBase *))); + d->mMaxPlayer.registerData(KGamePropertyBase::IdMaxPlayer, this, i18n("MaxPlayers")); + d->mMaxPlayer.setLocal(-1); // Infinite + d->mMinPlayer.registerData(KGamePropertyBase::IdMinPlayer, this, i18n("MinPlayers")); + d->mMinPlayer.setLocal(0); // Always ok + d->mGameStatus.registerData(KGamePropertyBase::IdGameStatus, this, i18n("GameStatus")); + d->mGameStatus.setLocal(Init); + // d->mUniquePlayerNumber = 0; + d->mRandom = new KRandomSequence; + d->mRandom->setSeed(0); + + connect(this, SIGNAL(signalClientConnected(Q_UINT32)), + this, SLOT(slotClientConnected(Q_UINT32))); + connect(this, SIGNAL(signalClientDisconnected(Q_UINT32,bool)), + this, SLOT(slotClientDisconnected(Q_UINT32,bool))); + connect(this, SIGNAL(signalConnectionBroken()), + this, SLOT(slotServerDisconnected())); + + setGameSequence(new KGameSequence()); + + // BL: FIXME This signal does no longer exist. When we are merging + // MH: super....and how do I find out about the lost conenction now? + // KGame and KGameNetwork, this could be improved! +// connect(this,SIGNAL(signalConnectionLost(KGameClient *)), +// this,SLOT(slotConnectionLost(KGameClient *))); +} + +KGame::~KGame() +{ + kdDebug(11001) << k_funcinfo << endl; +// Debug(); + reset(); + delete d->mGameSequence; + delete d->mRandom; + delete d; + kdDebug(11001) << k_funcinfo << " done" << endl; +} + +bool KGame::reset() +{ + deletePlayers(); + deleteInactivePlayers(); + return true; +} + +void KGame::deletePlayers() +{ +// kdDebug(11001) << k_funcinfo << endl; + KGamePlayerList tmp = d->mPlayerList; // in case of PolicyClean player=d->mPlayerList.first() is infinite + KPlayer *player; + while((player=tmp.first())) + { + delete player; // delete and removes the player + tmp.removeFirst(); + } +// kdDebug(11001) << k_funcinfo << " done" << endl; +} + +void KGame::deleteInactivePlayers() +{ + KPlayer *player; + while((player=d->mInactivePlayerList.first())) + { + //player->setGame(0); // prevent call backs + d->mInactivePlayerList.remove(player); + delete player; + } +} + +bool KGame::load(QString filename,bool reset) +{ + if (filename.isNull()) + { + return false; + } + QFile f(filename); + if (!f.open(IO_ReadOnly)) + { + return false; + } + QDataStream s( &f ); + load(s,reset); + f.close(); + return true; +} + +bool KGame::load(QDataStream &stream,bool reset) +{ return loadgame(stream, false,reset); } + +bool KGame::loadgame(QDataStream &stream, bool network,bool resetgame) +{ + // Load Game Data + + // internal data + Q_INT32 c; + stream >> c; // cookie + + if (c!=cookie()) + { + kdWarning(11001) << "Trying to load different game version we="<<cookie() << " saved=" << c << endl; + bool result=false; + emit signalLoadError(stream,network,(int)c,result); + return result; + } + if (resetgame) reset(); + + uint i; + stream >> i; +// setPolicy((GamePolicy)i); + + stream >> d->mUniquePlayerNumber; + + if (gameSequence()) + { + gameSequence()->setCurrentPlayer(0); // TODO !!! + } + int newseed; + stream >> newseed; + d->mRandom->setSeed(newseed); + + // Switch off the direct emitting of signals while + // loading properties. This can cause inconsistencies + // otherwise if a property emits and this emit accesses + // a property not yet loaded + // Note we habe to have this external locking to prevent the games unlocking + // to access the players + dataHandler()->lockDirectEmit(); + KPlayer *player; + for ( player=playerList()->first(); player != 0; player=playerList()->next() ) + { + player->dataHandler()->lockDirectEmit(); + // kdDebug(11001) << "Player "<<player->id() << " to indirect emit" <<endl; + } + + // Properties + dataHandler()->load(stream); + + // If there is additional data to be loaded before players are loaded then do + // this here. + emit signalLoadPrePlayers(stream); + + // Load Playerobjects + uint playercount; + stream >> playercount; + kdDebug(11001) << "Loading KGame " << playercount << " KPlayer objects " << endl; + for (i=0;i<playercount;i++) + { + KPlayer *newplayer=loadPlayer(stream,network); + systemAddPlayer(newplayer); + } + + Q_INT16 cookie; + stream >> cookie; + if (cookie==KGAME_LOAD_COOKIE) { + kdDebug(11001) << " Game loaded propertly"<<endl; + } else { + kdError(11001) << " Game loading error. probably format error"<<endl; + } + + // Switch back on the direct emitting of signals and emit the + // queued signals. + // Note we habe to have this external locking to prevent the games unlocking + // to access the players + dataHandler()->unlockDirectEmit(); + for ( player=playerList()->first(); player != 0; player=playerList()->next() ) + { + player->dataHandler()->unlockDirectEmit(); + // kdDebug(11001) << "Player "<<player->id() << " to direct emit" <<endl; + } + + emit signalLoad(stream); + return true; +} + +bool KGame::save(QString filename,bool saveplayers) +{ + if (filename.isNull()) + { + return false; + } + QFile f(filename); + if (!f.open(IO_WriteOnly)) + { + return false; + } + QDataStream s( &f ); + save(s,saveplayers); + f.close(); + return true; +} + +bool KGame::save(QDataStream &stream,bool saveplayers) +{ return savegame(stream, false,saveplayers); } + +bool KGame::savegame(QDataStream &stream,bool /*network*/,bool saveplayers) +{ + // Save Game Data + + // internal variables + Q_INT32 c=cookie(); + stream << c; + + uint p=(uint)policy(); + stream << p; + stream << d->mUniquePlayerNumber; + int newseed=(int)d->mRandom->getLong(65535); + stream << newseed; + d->mRandom->setSeed(newseed); + + // Properties + dataHandler()->save(stream); + + // Save all data that need to be saved *before* the players are saved + emit signalSavePrePlayers(stream); + + if (saveplayers) + { + savePlayers(stream,playerList()); + } + else + { + stream << (uint)0; // no players saved + } + + stream << (Q_INT16)KGAME_LOAD_COOKIE; + + emit signalSave(stream); + return true; +} + +void KGame::savePlayer(QDataStream &stream,KPlayer* p) +{ +// this could be in KGameMessage as well + stream << (Q_INT32)p->rtti(); + stream << (Q_INT32)p->id(); + stream << (Q_INT32)p->calcIOValue(); + p->save(stream); +} + +void KGame::savePlayers(QDataStream &stream, KGamePlayerList *list) +{ + if (!list) + { + list=playerList(); + } + + Q_INT32 cnt=list->count(); + kdDebug(11001) << "Saving KGame " << cnt << " KPlayer objects " << endl; + stream << cnt; + KPlayer *player; + for ( player=list->first(); player != 0; player=list->next() ) + { + savePlayer(stream,player); + } +} + +KPlayer *KGame::createPlayer(int /*rtti*/,int /*io*/,bool /*isvirtual*/) +{ + kdWarning(11001) << " No user defined player created. Creating default KPlayer. This crashes if you have overwritten KPlayer!!!! " << endl; + return new KPlayer; +} +KPlayer *KGame::loadPlayer(QDataStream& stream,bool isvirtual) +{ + Q_INT32 rtti,id,iovalue; + stream >> rtti >> id >> iovalue; + KPlayer *newplayer=findPlayer(id); + if (!newplayer) + { + kdDebug(11001) << k_funcinfo << "Player "<< id << " not found...asking user to create one " << endl; + newplayer=createPlayer(rtti,iovalue,isvirtual); + //emit signalCreatePlayer(newplayer,rtti,iovalue,isvirtual,this); + } + /* + if (!newplayer) + { + kdWarning(11001) << " No user defined player created. Creating default KPlayer. This crashes if you have overwritten KPlayer!!!! " << endl; + newplayer=new KPlayer; + } + else + { + kdDebug(11001) << " USER Player " << newplayer << " done player->rtti=" << newplayer->rtti() << " rtti=" << rtti << endl; + } + */ + newplayer->load(stream); + if (isvirtual) + { + newplayer->setVirtual(true); + } + return newplayer; +} + +// ----------------- Player handling ----------------------- + +KPlayer * KGame::findPlayer(Q_UINT32 id) const +{ + for (QPtrListIterator<KPlayer> it(d->mPlayerList); it.current(); ++it) + { + if (it.current()->id() == id) + { + return it.current(); + } + } + for (QPtrListIterator<KPlayer> it(d->mInactivePlayerList); it.current(); ++it) + { + if (it.current()->id() == id) + { + return it.current(); + } + } + return 0; +} + +// it is necessary that addPlayer and systemAddPlayer are called in the same +// order. Ie if addPlayer(foo) followed by addPlayer(bar) is called, you must +// not call systemAddPlayer(bar) followed by systemAddPlayer(foo), as the +// mAddPlayerList would get confused. Should be no problem as long as comServer +// and the clients are working correctly. +// BUT: if addPlayer(foo) does not arrive by any reason while addPlayer(bar) +// does, we would be in trouble... +void KGame::addPlayer(KPlayer* newplayer) +{ + kdDebug(11001) << k_funcinfo << ": " << "; maxPlayers=" << maxPlayers() << " playerCount=" << playerCount() << endl; + if (!newplayer) + { + kdFatal(11001) << "trying to add NULL player in KGame::addPlayer()" << endl; + return ; + } + + if (maxPlayers() >= 0 && (int)playerCount() >= maxPlayers()) + { + kdWarning(11001) << "cannot add more than " << maxPlayers() << " players - deleting..." << endl; + delete newplayer; + return; + } + + if (newplayer->id() == 0) + { + d->mUniquePlayerNumber++; + newplayer->setId(KGameMessage::createPlayerId(d->mUniquePlayerNumber, gameId())); + kdDebug(11001) << k_funcinfo << "NEW!!! player " << newplayer << " now has id " << newplayer->id() << endl; + } + else + { + // this could happen in games which use their own ID management by certain + // reasons. that is NOT recommended + kdDebug(11001) << k_funcinfo << "player " << newplayer << " already has an id: " << newplayer->id() << endl; + } + + QByteArray buffer; + QDataStream stream(buffer,IO_WriteOnly); + // We distinguis here what policy we have + if (policy()==PolicyLocal || policy()==PolicyDirty) + { + systemAddPlayer(newplayer); + } + if (policy()==PolicyClean || policy()==PolicyDirty) + { + savePlayer(stream,newplayer); + // Store the player for delayed clean adding + if (policy()==PolicyClean) + { + d->mAddPlayerList.enqueue(newplayer); + } + sendSystemMessage(stream,(int)KGameMessage::IdAddPlayer, 0); + } +} + +void KGame::systemAddPlayer(KPlayer* newplayer) +{ + if (!newplayer) + { + kdFatal(11001) << "trying to add NULL player in KGame::systemAddPlayer()" << endl; + return ; + } + if (newplayer->id() == 0) + { + kdWarning(11001) << k_funcinfo << "player " << newplayer << " has no ID" << endl; + } + + if (findPlayer(newplayer->id())) + { + kdError(11001) << "ERROR: Double adding player !!!!! NOT GOOD !!!!!! " << newplayer->id() << "...I delete it again" << endl; + delete newplayer; + } + else + { + kdDebug(11001) << "Trying to add player " << newplayer <<" maxPlayers="<<maxPlayers()<<" playerCount="<<playerCount() << endl; + // Add the player to the game + d->mPlayerList.append(newplayer); + newplayer->setGame(this); + kdDebug(11001) << "Player: isVirtual=" << newplayer->isVirtual() << endl; + kdDebug(11001) << " id=" << newplayer->id() << " #Players=" + << d->mPlayerList.count() << " added " << newplayer + << " (virtual=" << newplayer->isVirtual() << ")" << endl; + emit signalPlayerJoinedGame(newplayer); + } +} + +// Called by the KPlayer destructor +void KGame::playerDeleted(KPlayer *player) +{ + kdDebug(11001) << k_funcinfo << ": id (" << player->id() << ") to be removed " << player << endl; + + if (policy()==PolicyLocal || policy()==PolicyDirty) + { + systemRemovePlayer(player,false); + } + if (policy()==PolicyClean || policy()==PolicyDirty) + { + if (!player->isVirtual()) + { + kdDebug(11001) << k_funcinfo << ": sending IdRemovePlayer "<<player->id() << endl; + sendSystemMessage(player->id(), KGameMessage::IdRemovePlayer, 0); + } + } +} + +bool KGame::removePlayer(KPlayer * player, Q_UINT32 receiver) +{//transmit to all clients, or to receiver only + if (!player) + { + kdFatal(11001) << "trying to remove NULL player in KGame::removePlayer()" << endl; + return false; + } + kdDebug(11001) << k_funcinfo << ": id (" << player->id() << ") to be removed " << player << endl; + + if (policy()==PolicyLocal || policy()==PolicyDirty) + { + systemRemovePlayer(player,true); + } + if (policy()==PolicyClean || policy()==PolicyDirty) + { + kdDebug(11001) << k_funcinfo << ": sending IdRemovePlayer "<<player->id() << endl; + sendSystemMessage(player->id(),KGameMessage::IdRemovePlayer, receiver); + } + return true; + // we will receive the message in networkTransmission() +} + +void KGame::systemRemovePlayer(KPlayer* player,bool deleteit) +{ + kdDebug(11001) << k_funcinfo << endl; + if (!player) + { + kdWarning(11001) << "cannot remove NULL player" << endl; + return; + } + if (!systemRemove(player,deleteit)) + { + kdWarning(11001) << "player " << player << "(" << player->id() << ") Could not be found!" << endl; + } + + if (gameStatus()==(int)Run && playerCount()<minPlayers()) + { + kdWarning(11001) << k_funcinfo ": not enough players, PAUSING game\n" << endl; + setGameStatus(Pause); + } +} + +bool KGame::systemRemove(KPlayer* p,bool deleteit) +{ + if (!p) + { + kdWarning(11001) << "cannot remove NULL player" << endl; + return false; + } + bool result; + kdDebug(11001) << k_funcinfo << ": Player (" << p->id() << ") to be removed " << p << endl; + + if (d->mPlayerList.count() == 0) + { + result = false; + } + else + { + result = d->mPlayerList.remove(p); + } + + emit signalPlayerLeftGame(p); + + p->setGame(0); + if (deleteit) + { + delete p; + } + + return result; +} + +bool KGame::inactivatePlayer(KPlayer* player) +{ + if (!player) + { + return false; + } + kdDebug(11001) << "Inactivate player " << player->id() << endl; + + if (policy()==PolicyLocal || policy()==PolicyDirty) + { + systemInactivatePlayer(player); + } + if (policy()==PolicyClean || policy()==PolicyDirty) + { + sendSystemMessage(player->id(), KGameMessage::IdInactivatePlayer); + } + + return true; +} + +bool KGame::systemInactivatePlayer(KPlayer* player) +{ + if (!player || !player->isActive()) + { + return false; + } + kdDebug(11001) << " Inactivate player " << player->id() << endl; + + int pid=player->id(); + // Virtual players cannot be deactivated. They will be removed + if (player->isVirtual()) + { + systemRemovePlayer(player,true); + } + else + { + d->mPlayerList.remove(player); + d->mInactivePlayerList.prepend(player); + player->setActive(false); + } + emit signalPlayerLeftGame(player); + if (isAdmin()) + { + d->mInactiveIdList.prepend(pid); + } + return true; +} + +bool KGame::activatePlayer(KPlayer * player) +{ + if (!player) + { + return false; + } + kdDebug(11001) << k_funcinfo << ": activate " << player->id() << endl; + if (policy()==PolicyLocal || policy()==PolicyDirty) + { + systemActivatePlayer(player); + } + if (policy()==PolicyClean || policy()==PolicyDirty) + { + sendSystemMessage(player->id(), KGameMessage::IdActivatePlayer); + } + return true; +} + +bool KGame::systemActivatePlayer(KPlayer* player) +{ + if (!player || player->isActive()) + { + return false; + } + kdDebug(11001) << k_funcinfo << ": activate " << player->id() << endl; + + d->mInactivePlayerList.remove(player); + player->setActive(true); + addPlayer(player); + if (isAdmin()) + { + d->mInactiveIdList.remove(player->id()); + } + return true; +} + +// -------------------- Properties --------------------------- + +void KGame::setMaxPlayers(uint maxnumber) +{ if (isAdmin()) { d->mMaxPlayer.changeValue(maxnumber); } } + +void KGame::setMinPlayers(uint minnumber) +{ if (isAdmin()) { d->mMinPlayer.changeValue(minnumber); } } + +uint KGame::minPlayers() const +{ return d->mMinPlayer.value(); } + +int KGame::maxPlayers() const +{ return d->mMaxPlayer.value(); } + +uint KGame::playerCount() const +{ return d->mPlayerList.count(); } + +int KGame::gameStatus() const +{ return d->mGameStatus.value(); } + +bool KGame::isRunning() const +{ return d->mGameStatus.value() == Run; } + +KGamePropertyHandler* KGame::dataHandler() const +{ return d->mProperties; } + + +KGame::KGamePlayerList* KGame::inactivePlayerList() +{ return &d->mInactivePlayerList; } + +const KGame::KGamePlayerList* KGame::inactivePlayerList() const +{ return &d->mInactivePlayerList; } + +KGame::KGamePlayerList* KGame::playerList() +{ return &d->mPlayerList; } + +const KGame::KGamePlayerList* KGame::playerList() const +{ return &d->mPlayerList; } + +KRandomSequence* KGame::random() const +{ return d->mRandom; } + + +bool KGame::sendPlayerInput(QDataStream &msg, KPlayer *player, Q_UINT32 sender) +{ + if (!player) + { + kdError(11001) << k_funcinfo << ": NULL player" << endl; + return false; + } + if (!isRunning()) + { + kdError(11001) << k_funcinfo << ": game not running" << endl; + return false; + } + + kdDebug(11001) << k_funcinfo << ": transmitting playerInput over network" << endl; + sendSystemMessage(msg, (int)KGameMessage::IdPlayerInput, player->id(), sender); + return true; +} + +bool KGame::systemPlayerInput(QDataStream &msg, KPlayer *player, Q_UINT32 sender) +{ + if (!player) + { + kdError(11001) << k_funcinfo << ": NULL player" << endl; + return false; + } + if (!isRunning()) + { + kdError(11001) << k_funcinfo << ": game not running" << endl; + return false; + } + kdDebug(11001) << "KGame: Got playerInput from messageServer... sender: " << sender << endl; + if (playerInput(msg,player)) + { + playerInputFinished(player); + } + else + { + kdDebug(11001) << k_funcinfo<<": switching off player input"<<endl; + // TODO: (MH 03-2003): We need an return option from playerInput so that + // the player's is not automatically disabled here + if (!player->asyncInput()) + { + player->setTurn(false); // in turn based games we have to switch off input now + } + } + return true; +} + + +KPlayer * KGame::playerInputFinished(KPlayer *player) +{ + kdDebug(11001) << k_funcinfo<<"player input finished for "<<player->id()<<endl; + // Check for game over and if not allow the next player to move + int gameOver = 0; + if (gameSequence()) + { + gameSequence()->setCurrentPlayer(player); + } + // do not call gameSequence()->checkGameOver() to keep backward compatibility! + gameOver = checkGameOver(player); + if (gameOver!=0) + { + if (player) + { + player->setTurn(false); + } + setGameStatus(End); + emit signalGameOver(gameOver,player,this); + } + else if (!player->asyncInput()) + { + player->setTurn(false); // in turn based games we have to switch off input now + if (gameSequence()) + { + QTimer::singleShot(0,this,SLOT(prepareNext())); + } + } + return player; +} + +// Per default we do not do anything +int KGame::checkGameOver(KPlayer *player) +{ + if (gameSequence()) + { + return gameSequence()->checkGameOver(player); + } + return 0; +} + +void KGame::setGameSequence(KGameSequence* sequence) +{ + delete d->mGameSequence; + d->mGameSequence = sequence; + if (d->mGameSequence) + { + d->mGameSequence->setGame(this); + } +} + +KGameSequence* KGame::gameSequence() const +{ + return d->mGameSequence; +} + +void KGame::prepareNext() +{ + if (gameSequence()) + { + // we don't call gameSequence->nextPlayer() to keep old code working + nextPlayer(gameSequence()->currentPlayer()); + } +} + +KPlayer *KGame::nextPlayer(KPlayer *last,bool exclusive) +{ + if (gameSequence()) + { + return gameSequence()->nextPlayer(last, exclusive); + } + return 0; +} + +void KGame::setGameStatus(int status) +{ + kdDebug(11001) << k_funcinfo << ": GAMESTATUS CHANGED to" << status << endl; + if (status==(int)Run && playerCount()<minPlayers()) + { + kdDebug(11001) << k_funcinfo << ": not enough players, pausing game\n" << endl; + status=Pause; + } + d->mGameStatus = status; +} + +void KGame::networkTransmission(QDataStream &stream, int msgid, Q_UINT32 receiver, Q_UINT32 sender, Q_UINT32 /*clientID*/) +{//clientID is unused + // message targets a playerobject. If we find it we forward the message to the + // player. Otherwise we proceed here and hope the best that the user processes + // the message + +// kdDebug(11001) << k_funcinfo << ": we="<<(int)gameId()<<" id="<<msgid<<" recv=" << receiver << " sender=" << sender << endl; + + + // *first* notice the game that something has changed - so no return prevents + // this + emit signalMessageUpdate(msgid, receiver, sender); + if (KGameMessage::isPlayer(receiver)) + { + //kdDebug(11001) << "message id " << msgid << " seems to be for a player ("<<active=p->isActive()<<" recv="<< receiver << endl; + KPlayer *p=findPlayer(receiver); + if (p && p->isActive()) + { + p->networkTransmission(stream,msgid,sender); + return; + } + if (p) + { + kdDebug(11001) << "player is here but not active" << endl; + } + else + { + kdDebug(11001) << "no player found" << endl; + } + } + // If it is not for a player it is meant for us!!!! Otherwise the + // gamenetwork would not have passed the message to us! + + // GameProperties processed + if (d->mProperties->processMessage(stream, msgid, sender == gameId())) + { +// kdDebug(11001 ) << "KGame: message taken by property - returning" << endl; + return ; + } + + switch(msgid) + { + case KGameMessage::IdSetupGame: // Client: First step in setup game + { + Q_INT16 v; + Q_INT32 c; + stream >> v >> c; + kdDebug(11001) << " ===================> (Client) " << k_funcinfo << ": Got IdSetupGame ================== " << endl; + kdDebug(11001) << "our game id is " << gameId() << " Lib version=" << v << " App Cookie=" << c << endl; + // Verify identity of the network partners + if (c!=cookie()) + { + kdError(11001) << "IdGameSetup: Negotiate Game: cookie mismatch I'am="<<cookie()<<" master="<<c<<endl; + sendError(KGameError::Cookie, KGameError::errCookie(cookie(), c)); + disconnect(); // disconnect from master + } + else if (v!=KGameMessage::version()) + { + sendError(KGameError::Version, KGameError::errVersion(v)); + disconnect(); // disconnect from master + } + else + { + setupGame(sender); + } + kdDebug(11001) << "========== (Client) Setup game done\n"; + } + break; + case KGameMessage::IdSetupGameContinue: // Master: second step in game setup + { + kdDebug(11001) << "=====>(Master) " << k_funcinfo << " - IdSetupGameContinue" << endl; + setupGameContinue(stream, sender); + } + break; + case KGameMessage::IdActivatePlayer: // Activate Player + { + int id; + stream >> id; + kdDebug(11001) << "Got IdActivatePlayer id=" << id << endl; + if (sender!=gameId() || policy()!=PolicyDirty) + { + systemActivatePlayer(findPlayer(id)); + } + } + break; + case KGameMessage::IdInactivatePlayer: // Inactivate Player + { + int id; + stream >> id; + kdDebug(11001) << "Got IdInactivatePlayer id=" << id << endl; + if (sender!=gameId() || policy()!=PolicyDirty) + { + systemInactivatePlayer(findPlayer(id)); + } + } + break; + case KGameMessage::IdAddPlayer: + { + kdDebug(11001) << k_funcinfo << ": Got IdAddPlayer" << endl; + if (sender!=gameId() || policy()!=PolicyDirty) + { + KPlayer *newplayer=0; + // We sent the message so the player is already available + if (sender==gameId()) + { + kdDebug(11001) << "dequeue previously added player" << endl; + newplayer = d->mAddPlayerList.dequeue(); + } + else + { + newplayer=loadPlayer(stream,true); + } + systemAddPlayer(newplayer);// the final, local, adding + //systemAddPlayer(stream); + } + } + break; + case KGameMessage::IdRemovePlayer: // Client should delete player id + { + int id; + stream >> id; + kdDebug(11001) << k_funcinfo << ": Got IdRemovePlayer " << id << endl; + KPlayer *p=findPlayer(id); + if (p) + { + // Otherwise the player is already removed + if (sender!=gameId() || policy()!=PolicyDirty) + { + systemRemovePlayer(p,true); + } + } + else + { + kdWarning(11001) << k_funcinfo << "Cannot find player " << id << endl; + } + } + break; + case KGameMessage::IdGameLoad: + { + kdDebug(11001) << "====> (Client) " << k_funcinfo << ": Got IdGameLoad" << endl; + loadgame(stream,true,false); + } + break; + case KGameMessage::IdGameSetupDone: + { + int cid; + stream >> cid; + kdDebug(11001) << "====> (CLIENT) " << k_funcinfo << ": Got IdGameSetupDone for client " + << cid << " we are =" << gameId() << endl; + sendSystemMessage(gameId(), KGameMessage::IdGameConnected, 0); + } + break; + case KGameMessage::IdGameConnected: + { + int cid; + stream >> cid; + kdDebug(11001) << "====> (ALL) " << k_funcinfo << ": Got IdGameConnected for client "<< cid << " we are =" << gameId() << endl; + emit signalClientJoinedGame(cid,this); + } + break; + + case KGameMessage::IdSyncRandom: // Master forces a new random seed on us + { + int newseed; + stream >> newseed; + kdDebug(11001) << "CLIENT: setting random seed to " << newseed << endl; + d->mRandom->setSeed(newseed); + } + break; + case KGameMessage::IdDisconnect: + { + // if we disconnect we *always* start a local game. + // this could lead into problems if we just change the message server + if (sender != gameId()) + { + kdDebug(11001) << "client " << sender << " leaves game" << endl; + return; + } + kdDebug(11001) << "leaving the game" << endl; + // start a new local game + // no other client is by default connected to this so this call should be + // enough + setMaster(); + } + break; + default: + { + if (msgid < KGameMessage::IdUser) + { + kdError(11001) << "incorrect message id " << msgid << " - emit anyway" + << endl; + } + kdDebug(11001) << k_funcinfo << ": User data msgid " << msgid << endl; + emit signalNetworkData(msgid - KGameMessage::IdUser,((QBuffer*)stream.device())->readAll(),receiver,sender); + } + break; + } + +} + +// called by the IdSetupGameContinue Message - MASTER SIDE +// Here the master needs to decide which players can take part at the game +// and which will be deactivated +void KGame::setupGameContinue(QDataStream& stream, Q_UINT32 sender) +{ + KPlayer *player; + Q_INT32 cnt; + int i; + stream >> cnt; + + QValueList<int> inactivateIds; + + KGamePlayerList newPlayerList; + newPlayerList.setAutoDelete(true); + for (i=0;i<cnt;i++) + { + player=loadPlayer(stream,true); + kdDebug(11001) << " Master got player " << player->id() <<" rawgame=" << KGameMessage::rawGameId(player->id()) << " from sender " << sender << endl; + if (KGameMessage::rawGameId(player->id()) != sender) + { + kdError(11001) << "Client tries to add player with wrong game id - cheat possible" << endl; + } + else + { + newPlayerList.append(player); + kdDebug(11001) << " newplayerlist appended " << player->id() << endl; + } + } + + newPlayersJoin(playerList(),&newPlayerList,inactivateIds); + + + kdDebug(11001) << " Master calculates how many players to activate client has cnt=" << cnt << endl; + kdDebug(11001) << " The game has " << playerCount() << " active players" << endl; + kdDebug(11001) << " The user deactivated "<< inactivateIds.count() << " player already " << endl; + kdDebug(11001) << " MaxPlayers for this game is " << maxPlayers() << endl; + + // Do we have too many players? (After the programmer disabled some?) + // MH: We cannot use have player here as it CHANGES in the loop + // int havePlayers = cnt+playerCount()-inactivateIds.count(); + kdDebug(11001) << " havePlayers " << cnt+playerCount()-inactivateIds.count() << endl; + while (maxPlayers() > 0 && maxPlayers() < (int)(cnt+playerCount() - inactivateIds.count())) + { + kdDebug(11001) << " Still to deacticvate " + << (int)(cnt+playerCount()-inactivateIds.count())-(int)maxPlayers() + << endl; + KPlayer *currentPlayer=0; + int currentPriority=0x7fff; // MAX_UINT (16bit?) to get the maximum of the list + // find lowest network priority which is not yet in the newPlayerList + // do this for the new players + for ( player=newPlayerList.first(); player != 0; player=newPlayerList.next() ) + { + // Already in the list + if (inactivateIds.find(player->id())!=inactivateIds.end()) + { + continue; + } + if (player->networkPriority()<currentPriority) + { + currentPriority=player->networkPriority(); + currentPlayer=player; + } + } + + // find lowest network priority which is not yet in the newPlayerList + // Do this for the network players + for ( player=d->mPlayerList.first(); player != 0; player=d->mPlayerList.next() ) + { + // Already in the list + if (inactivateIds.find(player->id())!=inactivateIds.end()) + { + continue; + } + if (player->networkPriority()<currentPriority) + { + currentPriority=player->networkPriority(); + currentPlayer=player; + } + } + + // add it to inactivateIds + if (currentPlayer) + { + kdDebug(11001) << "Marking player " << currentPlayer->id() << " for inactivation" << endl; + inactivateIds.append(currentPlayer->id()); + } + else + { + kdError(11001) << "Couldn't find a player to dectivate..That is not so good..." << endl; + break; + } + } + + kdDebug(11001) << "Alltogether deactivated " << inactivateIds.count() << " players" << endl; + + QValueList<int>::Iterator it; + for ( it = inactivateIds.begin(); it != inactivateIds.end(); ++it ) + { + int pid=*it; + kdDebug(11001) << " pid=" << pid << endl; + } + + // Now deactivate the network players from the inactivateId list + //QValueList<int>::Iterator it; + for ( it = inactivateIds.begin(); it != inactivateIds.end(); ++it ) + { + int pid=*it; + if (KGameMessage::rawGameId(pid) == sender) + { + continue; // client's player + } + kdDebug(11001) << " -> the network needs to deactivate " << pid <<endl; + player=findPlayer(pid); + if (player) + { + // We have to make REALLY sure that the player is gone. With any policy + systemInactivatePlayer(player); + if (policy()!=PolicyLocal) + { + sendSystemMessage(player->id(), KGameMessage::IdInactivatePlayer); + } + } + else + { + kdError(11001) << " We should deactivate a player, but cannot find it...not good." << endl; + } + } + + // Now send out the player list which the client can activate + for ( player=newPlayerList.first(); player != 0; player=newPlayerList.next() ) + { + kdDebug(11001) << " newplayerlist contains " << player->id() << endl; + // Only activate what is not in the list + if (inactivateIds.find(player->id())!=inactivateIds.end()) + { + continue; + } + kdDebug(11001) << " -> the client can ******** reactivate ******** " << player->id() << endl; + sendSystemMessage(player->id(), KGameMessage::IdActivatePlayer, sender); + } + + // Save the game over the network + QByteArray bufferS; + QDataStream streamS(bufferS,IO_WriteOnly); + // Save game over netowrk and save players + savegame(streamS,true,true); + sendSystemMessage(streamS,KGameMessage::IdGameLoad,sender); + + + // Only to the client first , as the client will add players + sendSystemMessage(sender, KGameMessage::IdGameSetupDone, sender); +} + +// called by the IdSetupGame Message - CLIENT SIDE +// Client needs to prepare for network transfer +void KGame::setupGame(Q_UINT32 sender) +{ + QByteArray bufferS; + QDataStream streamS(bufferS,IO_WriteOnly); + + // Deactivate all players + KGamePlayerList mTmpList(d->mPlayerList); // we need copy otherwise the removal crashes + Q_INT32 cnt=mTmpList.count(); + kdDebug(11001) << "Client: playerlistcount=" << d->mPlayerList.count() << " tmplistcout=" << cnt << endl; + + streamS << cnt; + + QPtrListIterator<KPlayer> it(mTmpList); + KPlayer *player; + while (it.current()) + { + player=it.current(); + systemInactivatePlayer(player); + // Give the new game id to all players (which are inactivated now) + player->setId(KGameMessage::createPlayerId(player->id(),gameId())); + + // Save it for the master to decide what to do + savePlayer(streamS,player); + + ++it; + --cnt; + } + if (d->mPlayerList.count() > 0 || cnt!=0) + { + kdFatal(11001) << "KGame::setupGame(): Player list is not empty! or cnt!=0=" <<cnt << endl; + } + + sendSystemMessage(streamS,KGameMessage::IdSetupGameContinue,sender); +} + +// unused by KGame +void KGame::syncRandom() +{ + int newseed=(int)d->mRandom->getLong(65535); + sendSystemMessage(newseed,KGameMessage::IdSyncRandom); // Broadcast + d->mRandom->setSeed(newseed); +} + +void KGame::Debug() +{ + KGameNetwork::Debug(); + kdDebug(11001) << "------------------- KGAME -------------------------" << endl; + kdDebug(11001) << "this: " << this << endl; + kdDebug(11001) << "uniquePlayer " << d->mUniquePlayerNumber << endl; + kdDebug(11001) << "gameStatus " << gameStatus() << endl; + kdDebug(11001) << "MaxPlayers : " << maxPlayers() << endl; + kdDebug(11001) << "NoOfPlayers : " << playerCount() << endl; + kdDebug(11001) << "NoOfInactive: " << d->mInactivePlayerList.count() << endl; + kdDebug(11001) << "---------------------------------------------------" << endl; +} + +void KGame::slotClientConnected(Q_UINT32 clientID) +{ + if (isAdmin()) + { + negotiateNetworkGame(clientID); + } +} + +void KGame::slotServerDisconnected() // Client side +{ + kdDebug(11001) << "======= SERVER DISCONNECT ======="<<endl; + kdDebug(11001) << "+++ (CLIENT)++++++++" << k_funcinfo << ": our GameID="<<gameId() << endl; + + int oldgamestatus=gameStatus(); + + KPlayer *player; + KGamePlayerList removeList; + kdDebug(11001) << "Playerlist of client=" << d->mPlayerList.count() << " count" << endl; + kdDebug(11001) << "Inactive Playerlist of client=" << d->mInactivePlayerList.count() << " count" << endl; + for ( player=d->mPlayerList.first(); player != 0; player=d->mPlayerList.next() ) + { + // TODO: CHECK: id=0, could not connect to server in the first place?? + if (KGameMessage::rawGameId(player->id()) != gameId() && gameId()!=0) + { + kdDebug(11001) << "Player " << player->id() << " belongs to a removed game" << endl; + removeList.append(player); + } + } + + for ( player=removeList.first(); player != 0; player=removeList.next() ) + { + bool remove = true; + emit signalReplacePlayerIO(player, &remove); + if (remove) + { + kdDebug(11001) << " ---> Removing player " << player->id() << endl; + systemRemovePlayer(player,true); // no network necessary + } + } + + setMaster(); + kdDebug(11001) << " our game id is after setMaster " << gameId() << endl; + + KGamePlayerList mReList(d->mInactivePlayerList); + for ( player=mReList.first(); player != 0; player=mReList.next() ) + { + // TODO ?check for priority? Sequence should be ok + if ((int)playerCount()<maxPlayers() || maxPlayers()<0) + { + systemActivatePlayer(player); + } + } + kdDebug(11001) << " Players activated player-cnt=" << playerCount() << endl; + + for ( player=d->mPlayerList.first(); player != 0; player=d->mPlayerList.next() ) + { + int oldid=player->id(); + d->mUniquePlayerNumber++; + player->setId(KGameMessage::createPlayerId(d->mUniquePlayerNumber,gameId())); + kdDebug(11001) << "Player id " << oldid <<" changed to " << player->id() << " as we are now local" << endl; + } + // TODO clear inactive lists ? + Debug(); + for ( player=d->mPlayerList.first(); player != 0; player=d->mPlayerList.next() ) + { + player->Debug(); + } + kdDebug(11001) << "+++++++++++" << k_funcinfo << " DONE=" << endl; + emit signalClientLeftGame(0,oldgamestatus,this); +} + +void KGame::slotClientDisconnected(Q_UINT32 clientID,bool /*broken*/) // server side +{ + kdDebug(11001) << "++++(SERVER)+++++++" << k_funcinfo << " clientId=" << clientID << endl; + + int oldgamestatus=gameStatus(); + + KPlayer *player; + KGamePlayerList removeList; + kdDebug(11001) << "Playerlist of client=" << d->mPlayerList.count() << " count" << endl; + for ( player=d->mPlayerList.first(); player != 0; player=d->mPlayerList.next() ) + { + if (KGameMessage::rawGameId(player->id())==clientID) + { + kdDebug(11001) << "Player " << player->id() << " belongs to the removed game" << endl; + removeList.append(player); + } + } + + for ( player=removeList.first(); player != 0; player=removeList.next() ) + { + // try to replace the KGameIO first + bool remove = true; + emit signalReplacePlayerIO(player, &remove); + if (remove) { + // otherwise (no new KGameIO) remove the player + kdDebug(11001) << " ---> Removing player " << player->id() << endl; + removePlayer(player,0); + } + } + + // Now add inactive players - sequence should be ok + // TODO remove players from removed game + for (unsigned int idx=0;idx<d->mInactiveIdList.count();idx++) + { + QValueList<int>::Iterator it1 = d->mInactiveIdList.at(idx); + player = findPlayer(*it1); + if (((int)playerCount() < maxPlayers() || maxPlayers() < 0) && player && KGameMessage::rawGameId(*it1) != clientID) + { + activatePlayer(player); + } + } + emit signalClientLeftGame(clientID,oldgamestatus,this); +} + + +// -------------------- Synchronisation ----------------------- + +// this initializes a newly connected client. +// we send the number of players (including type) as well as game status and +// properties to the client. After the initialization has been completed both +// clients should have the same status (ie players, properties, etc) +void KGame::negotiateNetworkGame(Q_UINT32 clientID) +{ + kdDebug(11001) << "===========================" << k_funcinfo << ": clientID=" << clientID << " =========================== "<< endl; + if (!isAdmin()) + { + kdError(11001) << k_funcinfo << ": Serious WARNING..only gameAdmin should call this" << endl; + return ; + } + + QByteArray buffer; + QDataStream streamGS(buffer,IO_WriteOnly); + + // write Game setup specific data + //streamGS << (Q_INT32)maxPlayers(); + //streamGS << (Q_INT32)minPlayers(); + + // send to the newly connected client *only* + Q_INT16 v=KGameMessage::version(); + Q_INT32 c=cookie(); + streamGS << v << c; + sendSystemMessage(streamGS, KGameMessage::IdSetupGame, clientID); +} + +bool KGame::sendGroupMessage(const QByteArray &msg, int msgid, Q_UINT32 sender, const QString& group) +{ +// AB: group must not be i18n'ed!! we should better use an id for group and use +// a groupName() for the name // FIXME + KPlayer *player; + for ( player=d->mPlayerList.first(); player != 0; player=d->mPlayerList.next() ) + { + if (player && player->group()==group) + { + sendMessage(msg,msgid,player->id(), sender); + } + } + return true; +} + +bool KGame::sendGroupMessage(const QDataStream &msg, int msgid, Q_UINT32 sender, const QString& group) +{ return sendGroupMessage(((QBuffer*)msg.device())->buffer(), msgid, sender, group); } + +bool KGame::sendGroupMessage(const QString& msg, int msgid, Q_UINT32 sender, const QString& group) +{ + QByteArray buffer; + QDataStream stream(buffer, IO_WriteOnly); + stream << msg; + return sendGroupMessage(stream, msgid, sender, group); +} + +bool KGame::addProperty(KGamePropertyBase* data) +{ return dataHandler()->addProperty(data); } + +bool KGame::sendPlayerProperty(int msgid, QDataStream& s, Q_UINT32 playerId) +{ return sendSystemMessage(s, msgid, playerId); } + +void KGame::sendProperty(int msgid, QDataStream& stream, bool* sent) +{ + bool s = sendSystemMessage(stream, msgid); + if (s) + { + *sent = true; + } +} + +void KGame::emitSignal(KGamePropertyBase *me) +{ + emit signalPropertyChanged(me,this); +} + +KGamePropertyBase* KGame::findProperty(int id) const +{ return d->mProperties->find(id); } + +KGame::GamePolicy KGame::policy() const +{ + return d->mPolicy; +} +void KGame::setPolicy(GamePolicy p,bool recursive) +{ + // Set KGame policy + d->mPolicy=p; + if (recursive) + { + // Set all KGame property policy + dataHandler()->setPolicy((KGamePropertyBase::PropertyPolicy)p,false); + + // Set all KPLayer (active or inactive) property policy + for (QPtrListIterator<KPlayer> it(d->mPlayerList); it.current(); ++it) + { + it.current()->dataHandler()->setPolicy((KGamePropertyBase::PropertyPolicy)p,false); + } + for (QPtrListIterator<KPlayer> it(d->mInactivePlayerList); it.current(); ++it) + { + it.current()->dataHandler()->setPolicy((KGamePropertyBase::PropertyPolicy)p,false); + } + } +} + +/* + * vim: et sw=2 + */ |