diff options
Diffstat (limited to 'libkdegames/kgame')
53 files changed, 16189 insertions, 0 deletions
diff --git a/libkdegames/kgame/COMPAT b/libkdegames/kgame/COMPAT new file mode 100644 index 00000000..146d3a88 --- /dev/null +++ b/libkdegames/kgame/COMPAT @@ -0,0 +1,55 @@ +06.09.2001: replace the signal signalCreatePlayer by the virtual function + createPlayer. It has the same arguments but the return value + is the new player +06.09.2001: the KGameConfig dialog changes the parameter initConfigs from bool + to long. Use the ConfigOptions to specify what options you want + to have enabled. Default is all +06.09.2001: some int->Q_UINT32 in sender, receiver and player parameters. maybe + more will follow. +06.09.2001: KGameIO::signalPrepareMove(..., bool&) -> + KGameIO::signalPrepareMove(..., bool*): don't know why this was + necessary but it didn't work anymore... +16.09.2001: KGamePropertyHandler uses bool* for the sent parameter now. This is + because QT3 obviously doesn't honor referneces in signals/slots. + This might even be a QT bug. Bad situation - we use references + everywhere in KGame... hope nothing else is affecterd by this + problem (signalPrepareMove was fixed already by me) +18.09.2001: bool* for Key/Mouseevents and IOAdded in kgameio.h too +19.09.2001: Kgame:nextPlayer retunrs the KPlayer *nextplayer instead of bool +19.09.2001: gameOver() renamed to checkGameOver() !!!!! +18.09.2001: Question: Should the signal signalPlayerInput(QDataStream &,KPlayer *)) + be made a virtual function? + MH: This is done now. As this is a central function your programs will + not run anymore. Fix: rename your slot which is connected to the above + signal to playerInput() and return TRUE in it. This will make it 100% + compatible to the old version. I think this chagne is necessary especially + as a signal is of no use here as you cannot read twice from the same stream. + Therefore there can be only one function processing the input. If you really + need a signal, you can of course simply emit it in the overwritten playerInput + function +20.09.2001 playerInputFinished(void->KPlayer *) +--------------------- KGAME_ALPHA_1 --------------------- +06.10.2001 adding KGameNetwork::signalAdminStatusChanged - needed for + KGameDialog +06.10.2001 KGame::loadGame() doesn't call setPolicy() anymore! +08.10.2001 KGamePropertyList now honor policies! Use setPolicy(PolicyDirty) to + get the old behavior! + The behavior of KGamePropertyArray may have changed in this turn, + too! + The API stays the same. +11.10.2001 KGameDialogGeneralConfig now doesn't provide setMin/maxPlayers() + anymore. The game should manage this internally. layout() is + obsolete as well +18.10.2001 KPlayer::signalNetworkData contained QDataStream& instead of const + QByteArray& parameter (oops!). This is fixed now. All apps which + used this signal must be changed. +18.10.2001 KGame::sendProperty(), KGame::sendPlayerProperty(), + KPlayer::sendProperty() and related functions contain a "int msgid" + parameter. This is the id() of the property handler. This parameter + enables us to easily add any number of property handler to a game + just by connecting it to existing send slots and call + processMessage() in slotNetworkData() +03.11.2001 KPlayer::signalNetworkData now emits msgid-KGameMessage::IdUser just + like KGame::signalNetworkData does +06.11.2001 KGameDialog has some small improvements - easier and IMHO better + constructor code. Most code should be compatible :-) diff --git a/libkdegames/kgame/DESIGN b/libkdegames/kgame/DESIGN new file mode 100644 index 00000000..b1c48146 --- /dev/null +++ b/libkdegames/kgame/DESIGN @@ -0,0 +1,407 @@ +This document tries to describe the design of KGame - the KDE multiplayer +library. +This document has been written by: + Andreas Beckermann <[email protected]> + M. Heni <[email protected]> + Burkhard Lehner <[email protected]> + +This document is published under the terms of the GNU FDL + +!!! +Note that this is the initial version of this document and has not yet been +aproved by all core developers (and is far from being complete) +AB: please remove this comments as soon as all KGame hackers have read the +document +!!! + +Please refer the API documentation of every KGame class if you want up tp date +information. + + +0. Contents +----------- + +1. DEFINITIONS +1.1 Message Server +1.2 Client or Message Client +1.3 Master +1.4 Admin +1.5 Server +1.6 Player + +2. Game Negotiation (M.Heni 20.05.2001) + +AB: 3.x is obsolete! +3. Game Properties (Andreas Beckermann 28.07.2001) ( not yet completed ) +3.1 Using KGameProperty +3.2 Custom Classes +3.3 Concepts + +4. KGameIO (Andreas Beckermann 10.08.2001) + +5. Debugging (Andreas Beckermann 06.10.2001) TODO! +5.1 KGameDebugDialog +5.1.1 Debug KGame +5.1.3 Debug Messages + +--------------------------------------------------------------------- +1. DEFINITIONS +-------------- + +First we have to clear some words. The main expressions used in KGame which +need a definition are + +1.1 Message Server +1.2 Client or Message Client +1.3 Master +1.4 Admin +1.5 Server +1.6 Player + +The most important and confusing ones are Master, Admin and Server. We make +quite big differerences between those inside KGame. + +1.1 Message Server: +------------------- +A game has always exactly one object of this class, for local games as well as +for network games. For network games, this object can be on one of the users +processes (usually inside KGame), or it can also be on an independant computer, +that has no idea about what game is played on it. + +A KMessageClient object can connect to it. It's main purpose is transmitting +messages between KMessageClient objects. + +The Message Server is the main communication object. It is represented by the +class KMessageServer. Note that there is also a "Master" and a "Server" which +both differ heavily from the Message Server! + +1.2 Client, Message Client: +--------------------------- +Each process that wants to take part in the game must have a +KMessageClient object, that is connected to the Message Server. KGame creates +this object and connects it to the Messager Server, so that you usually don't +need to create these of your own. Even in a local game (no network) there +must be a message server and one message client connected to it. This is usually +done by the KGame object itself. + +Each message client has a unique ID number (a positive integer value, not zero). +The KMessageClient object, which does the communication with the Message Server +is called "Message Client" and to simplify the use we call any KGame object (or +even the game process) that is connected to a game (i.e. even the Master) just +"Client". + +The main purpose of a Client is to connect to a Master (i.e. to a game) and to +communicate with it. A client has always a KGame object. + +1.3 Master: +----------- +The process that contains the Message Server is called "Master". In any local +game this is the game process. The Message Server is started by KGame using +KGame::setMaster(true) which is automatically done on startup. The Message +Server is deleted automatically as soon as you connect to another Master. +So in most cases there is exactly one KGame object / Client which is Master. But +in one case there can be no KGame object / Client that is Master - if the +Message Server is started as an own process. This "Message-Server-only" process +is called "Master" then, although there is no KGame object which is Master. See +also the definition of Admin! + +1.4 Admin: +---------- +One (and only one) of the Clients is the Admin. He can configure the Message +Server and the game in general in several ways. He can limit the maximum number +of connected message clients and can drop the connection to some other clients, +as well as he can configure game specific ssettings (like max/min players, start +money, ...). The Admin also initializes newly connected Clients. If the Admin +himself disconnects, another Client becomes Admin (The Admin can himself elect +some other Client to become Admin. He himself loses that Admin status then). +An Admin is *alway* a KGame object. The Admin is usually the same as the Master, +but if the Master is an own process (i.e. the Message Server has been started +outside KGame) then Master and Admin differ. An Admin *must* be a KGame object +while the Master doesn't have to be. + +1.5 Server: +----------- +The definition of Server differs quite much from the definition of Master. +A Master just accepts connections and forwards messages. The Server on the other +side checks these messages, calculates results and sends the results to the +Clients. That means the Server does all game calculations and doesn't directly +forward the messages from one Clients to all other Clients. +KGamer makes it possible to write multiplayer games even without a Server. All +Clients just send their moves to the Master which forwards them to all Clients. +Now all Clients calculate the result. +E.g. in a poker game a player selects two of five cards to be exchanges and +clicks on "draw" then the client sends the message "Exchange Card-1 and Card-2" +to the Master. A no-Server solution forwards this to all Clients, and these +Clients exchange the cards of the player. Note that in a no-Server solution +(you can also see it as a "every-Client-is-a-Server solution") all Clients must +have the same random seed and must be of the same version, i.e. the result must +be the same on all Clients. +In a Server-Solution on the other hand the Master forwards the Message +("Exchange Card-1 and Card-2") to the Server only. This Server now calculates +the result, and sends the new cards back to the Client. +Both concepts have advantages and disadvantages. It is on you - the game +developer - to decide which way is better for you. +E.g. the Server-Solution makes it easier for you to write games. The version +must not necessarily be the same, you have one central computer which does the +calcultations. The No-Server-Solution on the other hand decreases network +traffik as the Clients just send their moves and all Clients can calculate the +reactions. I'm sure there are a lot of advantages/disadvantages more for both +concepts. + +1.6 Player: +----------- +A KPlayer object is always connected to a KGame object and represents a +player that participates the game. In a network game, every KPlayer object is +duplicated on every other KGame object connected to the message server with +virtual KPlayer objects. So at every time in the game, every KGame object has +the same number of KPlayer objects. + + +2. Game negotiation +------------------- +Upon connection of a client the admin and the client try to negotiate +the game setup. Basically this means the game of the admin is transferred +(saved) on the client. However, the client's players are added to the game +as far as possible. If the addition of the client's players would add more +players than allowed some players are inactivated. Which players are +inactivated depends on their networkPriority(). This procedure allows +easy replacement of players in a constant number game (e.g. chess). If +this feature is of no interest simply keep the priorities equal (all 0) +and the client will only add only players if the number of players is +less or equal the maximum player number. + +The following is the negotiation procedure as started by the connection +of a client. It is initiated in the negotiateNetworkGame() virtual function +of KGame: + +admin: client: +------------ ------------ +IdSetupGame + QINT16 Library + Version + QINT32 Application + cookie + IdSetupGameContinue; + QValueList<int> player id's + QValueList<int> network priority's + +IdGameLoad + all game data + +IdGameReactivate + QValueList<int> id's + +IdSyncRandom + int randomseed + + +3. Game Properties +------------------ +A very hard task in a network game is consistency. You have to achieve that all +properties of the game and of all players have the same value on all clients +every time. This is because +a) the user might be confused if he sees "Player has $0" on client A but +"Player has $10" on client B and +b) Often game handling depends on those values, e.g. if the example above +happens the computer might quit the game for the Player on client A because +he/she doesn't have enough money. But the game continues on client B. +Another not that easy task is the network protocol itself. You have to write +several send() and receive() functions which apply changed values of properties +to the local property. + +KGameProperty is designed to do all of this for you. KGameProperty is +implemented as a template so you can use it theoretically for every type of data +- even for your self defined classes. + + +3.1 Using KGameProperty +----------------------- +It is basically very easy to use a KGameProperty. You first create your own +class containing the property, e.g: +class MyGame : public KGame +{ +[...] +protected: + KGamePropertyInt money; + KGamePropertyQString name; + KGameProperty<AntotherClass> myProperty; +}; +KGamePropertyInt is just a typedef for KGameProperty<int> - just like +KGamePropertyQString. Now you need to register the properties in the constructor +of the class to the KGamePropertyHandler: +MyGame::MyGame() : KGame(myCookie) +{ + money.registerData(KGamePropertyBase::IdUser+1, dataHandler(), "Money"); + name.registerData(KGamePropertyBase::IdUser+2, this, "Name"); + myProperty.registerData(KGamePropertyBase::IdUser+3, dataHandler(), "MyProperty"); +} +-> You need to specify a *unique* ID. This ID must be greater than +KGamePropertyBase::IdUser. IDs below this are reserved for KGame. Probably this +will be changed so that you cannot use IDs below IdUser in the future. Then you +have to specify the dataHandler(). You can also use a KGame or KPlayer pointer. +This will automatically use KGame::dataHandler() or KPlayer::dataHandler(). +Finally you *can* provide a name for the property. This will be used for +debugging in KGameDebugDialog. If you want to save some memory you can leave +this out. +Note that if you use pointers to create the properties dynamically they are +*not* deleted automatically! You MUST delete them yourself! +Now you can use the KGameProperty like every variable else. See also Section +"3.3 Concepts" for restrictions in use. + +3.2 Custom Classes +------------------ +To make custom classes possible you have to implement several operators for your +them: you need at least << and >> for QDataStream as well as "==" for your own +class. To overload the "<<" you would e.g. do something like this: +QDataStream& operator<<(QDataStream& stream, MyData& data) +{ + int type = data.type; + QString name = data.name; + stream << type << name; + return stream; +} +So you basically just have to split your class to several basic types and stream +them. + +3.3 Concepts +------------ +You can use KGameProperty basically in two completely different ways. You can +also use a mixture of both but this is not recommended. The default behaviour +and therefore also the recommended is the "clean" way: +a) Always Consistent. This means that a KGameProperty has always the same value +on *every* client. This is achieved by using KGameProperty::send() whenever you +want to change the value using "=". You can still use changeValue() or +setLocal() but send() will be the default. If you use send() then the value of +the property does *NOT* change immediately. It is just sent to the +KMessageServer which forwards the value to all clients. As soon as the new value +is received from the message server the KGamePropertyHandler (a collection class +for KGameProperty) calls KGameProperty::load() and changes the value of the +property. So the game first has to go into the event loop, where the message is +received. This means to you that you cannot do this: +myIntProperty = 10; +int value = myIntProperty; +As myIntPoperty still has the old value when "value = myIntProperty" is called. +This might seem to be quite complex, but +KGamePropertyHandler::signalPropertyChanged() is emitted whenever a new value is +assigned so you can connect to this and work immediately with the new value. +You gain the certainty that the value is the same on every client every time. +That will safe you a lot of time debugging! +Another way is the "dirty" way: +b) Not Always Consistent. Sometimes you really *want* to do something like +myIntProperty = 10; +int value = myIntProperty; +but this is not possible with the default behaviour. If you call +KGameProperty::setAlwaysConsistent(false) in the constructor (right after +registerData()) you get another behaviour. "=" means changeValue() now. +changeValue() also uses send() to change the value but additionally calls +setLocal() to create a local copy of the property. This copy now has the value +you supplied with "=" and is deleted again as soon as any value from the network +is received. + +4. KGameIO +---------- +The class KGameIO is used to let the players communicate with the server. You +can plug as many KGameIO objects into a player as you want, e.g. you can plug a +KGameMouseIO and a KGameKeyIO into a player so that you can control the player +with the mouse and the keyboard - e.g. in a breakout game. +You can probably see the advantage: as most of the control stuff is common in a +lot of games you can use the same IO class in many different games with very +small adjustments. +You could also put all the IO stuff directly into your KPlayer object, like +sendBet(int money) for a poker game. But there is a major disadvantage and I'm +very sure you don't want to use a KPlayer object for your IO stuff as soon as +you know which disadvantage: +KGameIO is designed to be able to switch between different IOs "on the fly". So +you might have a KGamePlayerIO, derived from KGameIO, for your game. But now +this player (who "owns"/uses the KGamePlayerIO) leaves the game (e.g. because he +was a remote player). So now the game would be over for every player as one +player is now out of order. But with KGameIO you can just let any of the +remaining clients create a KGameComputerIO and plug this into the player. So the +player now is controlled by the computer and the game can continue. + +Think about it! You don't have to care about removing players when a player +leaves as you can just replace it! The same works the other way round: imagine a +game with 10 player (e.g. 5 human and 5 computer players) that has already +started. You cannot add any further players without restarting. So if there are +any additional player you can just call KPlayer::removeGameIO() which removes +the IO of a computer player and then call KPlayer::addGameIO() for the same +player which adds a GameIO for new human player. That's all! + +To achieve this you just have to make sure that you make *all* of your IO +operations through a KGameIO! So instead of using MyPlayer::sendBet(int money) +you should use something like MyIO::sendBet(). The amount of money would +probably be calculated by the game IO itself. + + + +5. Debugging +------------ +The general debugging concept (if there is one at all) or general debugging +hints are not yet written. Feel free to do so + +5.1 KGameDebugDialog +-------------------- +A nice way of debugging a KGame based game is the KGameDebugDialog. Basically +all you have to do is to add something like "Debug" to your game's menu and add +a slot like +slotDebug() +{ + KGameDebugDialog* dialog = new KGameDebugDialog(mGame, this); + connect(dialog, SIGNAL(finished()), dialog, SLOT(slotDelayedDestruct())); + dialog->show(); +} +that's it. +You can now click on that menu entry and you get a non-modal dialog where you +can start to debug :-) +The dialog consist of several pages. You can easily add your own using +KDialogBase::addVBoxPage() (for example). + +5.1.1 Debug KGame +----------------- +The first page, "Debug KGame" shows on the left most or even all status values of +KGame. That contains e.g. minPlayers(), isAdmin(), gameStatus(), ... +The right side is probably the more important one. It lists *all* KGameProperties +which have been inserted to this KGame object (only to this KGame object - not +the ones that have been added to the players!). Most of the status variables of +the left side are here again as they are implemented as KGameProperty. You can +see the name of the property (together with its ID), its value and the policy +this property uses. Note that "unknwon" will be displayed as name of the +property if you haven't supplied one. See KGamePropertyBase::registerData() for +info. You probably always want to supply a name for the property to debug it +easily. In the future there will be something like +KGamePropertyHandler::setDebug() so that you can switch off debugging and save +the memory of the names in a release version. +For as long as you use standard types for your properties (int, long, bool, +...) you should always be able to see the value of the property. If you just see +"unknown" then this type has not been implemented. You can connect to the signal +KGamePropertyHandler::signalRequestValue() and supply a QString with the value +yourself. If you do so for a standard type please also submit a bug report! + +Currently the dialog does *not* update automatically! So you alway have to click +the "update" button when you want a current value. There are several reasons for +this (and one of them is that i'm too lazy to implement the update ;)). E.g. +often (very often) a property is just in the background - stores e.g. the +available money in a game. But you don't want it to update whenever the value +changes (a player receives/pays money) but only when the value on the screen +changes. + +5.1.2 Debug Players +------------------- +This page consists of three widgets. On the very left there is a list of all +players in the game. Only the IDs are displayed to save space. If you click one +the other widgets are filled with content. These widgets are quite much the same +as the ones in "Debug KGame" - the left shows the value of the functions and the +right one displays all KProperties of a player. Not much to say here - except: +See "Debug KGame". + +If you change to another player the value are also updated. + +5.1.3 Debug Messages +-------------------- +This page is probably not as important as the other ones. It displays *every* +message that is sent through the KGame object. As a KGameProperry also send +messages you probably get a lot of them... +You can exclude message IDs from being displayed (e.g. all game properties). +You can also change the sorting of the list to see all messages of a certain ID. +The default is to sort by time (which is displayed on the left side). + diff --git a/libkdegames/kgame/Makefile.am b/libkdegames/kgame/Makefile.am new file mode 100644 index 00000000..e0117780 --- /dev/null +++ b/libkdegames/kgame/Makefile.am @@ -0,0 +1,29 @@ + +noinst_LTLIBRARIES = libkgame.la + +# compile-order doesn't matter here but maybe we will split these section soon + +KGAME = kgame.cpp kplayer.cpp kgamenetwork.cpp kgameproperty.cpp \ + kgamemessage.cpp kgameio.cpp kgameprocess.cpp kgamechat.cpp \ + kgamepropertyhandler.cpp kgameerror.cpp kgamesequence.cpp +KGAME_H = kgame.h kplayer.h kgamenetwork.h kgameproperty.h kgamemessage.h \ + kgameio.h kgameprocess.h kgamepropertyarray.h \ + kgamepropertylist.h kgamechat.h kgamepropertyhandler.h \ + kgameerror.h kgamesequence.h kgameversion.h + +KMESSAGE = kmessageio.cpp kmessageserver.cpp kmessageclient.cpp +KMESSAGE_H = kmessageio.h kmessageserver.h kmessageclient.h + +libkgameincludedir=$(includedir)/kgame +libkgame_la_SOURCES = $(KMESSAGE) $(KGAME) + +libkgameinclude_HEADERS = $(KMESSAGE_H) $(KGAME_H) + +INCLUDES = -I$(top_srcdir)/libkdegames $(all_includes) +METASOURCES = AUTO + +SUBDIRS = . dialogs + +messages: +# $(XGETTEXT) `find . -name \*.h -o -name \*.cpp -o -name \*.cc` -o $(podir)/libkdegames.pot + diff --git a/libkdegames/kgame/README.LIB b/libkdegames/kgame/README.LIB new file mode 100644 index 00000000..512edbac --- /dev/null +++ b/libkdegames/kgame/README.LIB @@ -0,0 +1,12 @@ +some thoughts and comments about the lib - usually for KGame hackers + +- setMin/MaxPlayers() etc. use KGameProperty::changeValue() which is slightly + unclean but as these functions can only called by the ADMIN it doesn't matter. +- AB: KGamePropertyList && KGamePropertyArray: + for PolicyClean||PolicyDirty the values are streamed into a QDataStream as usual + for PolicyDirty||PolicyLocal the values are streamed as well but + additionally command() is called immediately. The values are read from + the stream there. This is some kind of performance loss as it would be + faster *not* to stream it but imediately call e.g. insert(). But it will + probably save a *lot* of bugs! + diff --git a/libkdegames/kgame/TODO b/libkdegames/kgame/TODO new file mode 100644 index 00000000..2f100b8a --- /dev/null +++ b/libkdegames/kgame/TODO @@ -0,0 +1,41 @@ +- 28.02.2001: Direct computer player for kpoker like games support needs to be + improved. UPDATE (01/10/06): but what is needed there? +- 05.03.2001: Documentation. I am thinking of an explaination of the + class + methods and example code for the "key" methods. A sample + implementation in a small game (like the current kdenonbeta/kgame + but with a real sense - mabye use the QT tic-tac-toe example?) + would be very great (this could be stuff of a tutorial instead of + KGame documentation) + MH: Even better idea +- 03.06.2001: can KGameNetwork::sendSystemMessage be made protected (maybe using + friends)? sendSystenMessage AND sendMessage is very confusing to + the user... +- 03.06.2001: can we translate the group of a KPlayer? Probably not as there are + no international connections possible then... maybe a group id? +- 05.06.2001: KGameDialog::saveConfig(KConfig*) might be useful (as well as + KGameDialog::loadConfig(KConfig*). Should set an own group in the + config file (setGroup("KGameDialog")). Problem: shalll network + settings be saved? Could be used for startup configuration (i.e. + load the config of the previous game) otherwise. +- 21.06.2001: KPlayerPropertyArray does not yet support at() and operator[] + assignments. Need to check whether the method from QBitArray + can be applied +- 02.04.2001: VERY DANGEROUS: property1=property2 does NOT assign the values, e.g. int + but assignes the whole property, i.e. you have then two properties with + the same id and everything is wrong + 01/09/09: FIXED! (AB) TODO: check if this behavior also appears in + KGamePropertyList and KGamePropertyArray. Althogh this should not + be the case +- 23.09.2001: does the virtual destructor make sense for KGamePropertyBase? +- 29.09.2001: GGZ integration. I (Andi) already volunteered for this - it's just + here so that I don't forget it +- 06.10.2001: add KGamePropertyHandler::setDebug(false) to clear all debug names + (and to not accept new names) of KGameProperty. Will save some + memory +- 06.10.2001: If one kicks a player (in KGameDialog) the client should be kicked + as well. Perhaps always disconnect a client when all players from + it have disappeared? +- 07.10.2001: display (List) or (Array) for KGameProperty[List|Array] in + KGameDebugDialog as value +- 08.10.2001: KGamePropertyList|KGamePropertyArray must be ported to new QT3 API + (e.g. erase instead of remove, ...) diff --git a/libkdegames/kgame/dialogs/Makefile.am b/libkdegames/kgame/dialogs/Makefile.am new file mode 100644 index 00000000..1b9de53c --- /dev/null +++ b/libkdegames/kgame/dialogs/Makefile.am @@ -0,0 +1,17 @@ + +noinst_LTLIBRARIES = libkgamedialogs.la + +# compile-order doesn't matter here but maybe we will split these section soon + + +libkgamedialogs_la_SOURCES = kgamedialog.cpp kgameconnectdialog.cpp kgameerrordialog.cpp kgamedebugdialog.cpp kgamedialogconfig.cpp + +libkgamedialogsincludedir=$(includedir)/kgame +libkgamedialogsinclude_HEADERS = kgamedialog.h kgameconnectdialog.h kgameerrordialog.h kgamedebugdialog.h kgamedialogconfig.h + +INCLUDES = -I$(top_srcdir)/libkdegames -I$(top_srcdir)/libkdegames/kgame $(all_includes) +METASOURCES = AUTO + +messages: +# $(XGETTEXT) `find . -name \*.h -o -name \*.cpp -o -name \*.cc` -o $(podir)/libkdegames.pot + diff --git a/libkdegames/kgame/dialogs/kgameconnectdialog.cpp b/libkdegames/kgame/dialogs/kgameconnectdialog.cpp new file mode 100644 index 00000000..4d2d90e0 --- /dev/null +++ b/libkdegames/kgame/dialogs/kgameconnectdialog.cpp @@ -0,0 +1,278 @@ +/* + This file is part of the KDE games library + Copyright (C) 2001 Andreas Beckermann ([email protected]) + Copyright (C) 2001 Martin Heni ([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. +*/ + + +#include "kgameconnectdialog.h" + +#include <knuminput.h> +#include <klocale.h> + +#include <qlineedit.h> +#include <qcombobox.h> +#include <qvbuttongroup.h> +#include <qlayout.h> +#include <qradiobutton.h> +#include <qlabel.h> +#include <dnssd/servicebrowser.h> +#include <qpushbutton.h> +#include <qgrid.h> + +class KGameConnectWidgetPrivate +{ + public: + KGameConnectWidgetPrivate() + { + mPort = 0; + mHost = 0; + mButtonGroup = 0; + mBrowser = 0; + } + + KIntNumInput* mPort; + QLineEdit* mHost; //KLineEdit? + QVButtonGroup* mButtonGroup; + QComboBox *mClientName; + QLabel *mClientNameLabel; + DNSSD::ServiceBrowser *mBrowser; + QLabel *mServerNameLabel; + QLineEdit *mServerName; + QString mType; +}; + +KGameConnectWidget::KGameConnectWidget(QWidget* parent) : QWidget(parent) +{ + d = new KGameConnectWidgetPrivate; + + QVBoxLayout* vb = new QVBoxLayout(this, KDialog::spacingHint()); + d->mButtonGroup = new QVButtonGroup(this); + vb->addWidget(d->mButtonGroup); + connect(d->mButtonGroup, SIGNAL(clicked(int)), this, SLOT(slotTypeChanged(int))); + (void)new QRadioButton(i18n("Create a network game"), d->mButtonGroup); + (void)new QRadioButton(i18n("Join a network game"), d->mButtonGroup); + + QGrid* g = new QGrid(2, this); + vb->addWidget(g); + g->setSpacing(KDialog::spacingHint()); + d->mServerNameLabel = new QLabel(i18n("Game name:"), g); + d->mServerName = new QLineEdit(g); + d->mClientNameLabel = new QLabel(i18n("Network games:"), g); + d->mClientName = new QComboBox(g); + connect(d->mClientName,SIGNAL(activated(int)),SLOT(slotGameSelected(int))); + (void)new QLabel(i18n("Port to connect to:"), g); + d->mPort = new KIntNumInput(g); + (void)new QLabel(i18n("Host to connect to:"), g); + d->mHost = new QLineEdit(g); + + QPushButton *button=new QPushButton(i18n("&Start Network"), this); + connect(button, SIGNAL(clicked()), this, SIGNAL(signalNetworkSetup())); + vb->addWidget(button); + // Hide until type is set + d->mClientName->hide(); + d->mClientNameLabel->hide(); + d->mServerName->hide(); + d->mServerNameLabel->hide(); +} + +void KGameConnectWidget::showDnssdControls() +{ + if (!d->mBrowser) return; + if (d->mHost->isEnabled()) { // client + d->mClientName->show(); + d->mClientNameLabel->show(); + d->mServerName->hide(); + d->mServerNameLabel->hide(); + slotGameSelected(d->mClientName->currentItem()); + } else { + d->mClientName->hide(); + d->mClientNameLabel->hide(); + d->mServerName->show(); + d->mServerNameLabel->show(); + } +} + +void KGameConnectWidget::setType(const QString& type) +{ + d->mType = type; + delete d->mBrowser; + d->mBrowser = new DNSSD::ServiceBrowser(type); + connect(d->mBrowser,SIGNAL(finished()),SLOT(slotGamesFound())); + d->mBrowser->startBrowse(); + showDnssdControls(); +} + +void KGameConnectWidget::slotGamesFound() +{ + bool autoselect=false; + if (!d->mClientName->count()) autoselect=true; + d->mClientName->clear(); + QStringList names; + QValueList<DNSSD::RemoteService::Ptr>::ConstIterator itEnd = d->mBrowser->services().end(); + for (QValueList<DNSSD::RemoteService::Ptr>::ConstIterator it = d->mBrowser->services().begin(); + it!=itEnd; ++it) names << (*it)->serviceName(); + d->mClientName->insertStringList(names); + if (autoselect && d->mClientName->count()) slotGameSelected(0); +} + +void KGameConnectWidget::setName(const QString& name) +{ + d->mServerName->setText(name); +} + +QString KGameConnectWidget::gameName() const +{ + return d->mServerName->text(); +} + +QString KGameConnectWidget::type() const +{ + return d->mType; +} + +void KGameConnectWidget::slotGameSelected(int nr) +{ + if (nr>=(d->mBrowser->services().count()) || nr<0) return; + if (!d->mHost->isEnabled()) return; // this is server mode, do not overwrite host and port controls + DNSSD::RemoteService::Ptr srv = d->mBrowser->services()[nr]; + if (!srv->isResolved() && !srv->resolve()) return; + d->mHost->setText(srv->hostName()); + d->mPort->setValue(srv->port()); +} +KGameConnectWidget::~KGameConnectWidget() +{ + delete d->mBrowser; + delete d; +} + +QString KGameConnectWidget::host() const +{ + if (d->mHost->isEnabled()) { + return d->mHost->text(); + } else { + return QString::null; + } +} + +unsigned short int KGameConnectWidget::port() const +{ + return d->mPort->value(); +} + +void KGameConnectWidget::setHost(const QString& host) +{ + d->mHost->setText(host); +} + +void KGameConnectWidget::setPort(unsigned short int port) +{ + d->mPort->setValue(port); +} + +void KGameConnectWidget::setDefault(int state) +{ + d->mButtonGroup->setButton(state); + slotTypeChanged(state); +} + +void KGameConnectWidget::slotTypeChanged(int t) +{ + if (t == 0) { + d->mHost->setEnabled(false); + } else if (t == 1) { + d->mHost->setEnabled(true); + } + showDnssdControls(); + emit signalServerTypeChanged(t); +} + +class KGameConnectDialogPrivate +{ + public: + KGameConnectDialogPrivate() + { + mConnect = 0; + } + + KGameConnectWidget* mConnect; +}; + +// buttonmask =Ok|Cancel +KGameConnectDialog::KGameConnectDialog(QWidget* parent,int buttonmask) : KDialogBase(Plain, + i18n("Network Game"),buttonmask , Ok, parent, 0, true, buttonmask!=0) +{ + d = new KGameConnectDialogPrivate; + QVBoxLayout* vb = new QVBoxLayout(plainPage(), spacingHint()); + d->mConnect = new KGameConnectWidget(plainPage()); + vb->addWidget(d->mConnect); +} + +KGameConnectDialog::~KGameConnectDialog() +{ + delete d; +} + +int KGameConnectDialog::initConnection( unsigned short int& port, + QString& host, QWidget* parent, bool server) +{ + KGameConnectDialog d(parent); + d.setHost(host); + d.setPort(port); + if (server) { + d.setDefault(0); + } else { + d.setDefault(1); + } + + int result = d.exec(); + if (result == QDialog::Accepted) { + host = d.host(); + port = d.port(); + } + return result; +} + +QString KGameConnectDialog::host() const +{ + return d->mConnect->host(); +} + +unsigned short int KGameConnectDialog::port() const +{ + return d->mConnect->port(); +} + +void KGameConnectDialog::setHost(const QString& host) +{ + d->mConnect->setHost(host); +} + +void KGameConnectDialog::setPort(unsigned short int port) +{ + d->mConnect->setPort(port); +} + +void KGameConnectDialog::setDefault(int state) +{ + d->mConnect->setDefault(state); +} + + + +#include "kgameconnectdialog.moc" + diff --git a/libkdegames/kgame/dialogs/kgameconnectdialog.h b/libkdegames/kgame/dialogs/kgameconnectdialog.h new file mode 100644 index 00000000..acbf21d2 --- /dev/null +++ b/libkdegames/kgame/dialogs/kgameconnectdialog.h @@ -0,0 +1,169 @@ +/* + 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. +*/ + +#ifndef __KGAMECONNECTDIALOG_H__ +#define __KGAMECONNECTDIALOG_H__ + +#include <kdialogbase.h> + +class KGameConnectDialogPrivate; +class KGameConnectWidgetPrivate; + +class KGameConnectWidget : public QWidget +{ + Q_OBJECT +public: + KGameConnectWidget(QWidget* parent); + virtual ~KGameConnectWidget(); + + /** + * @param host The host to connect to by default + **/ + void setHost(const QString& host); + + /** + * @return The host to connect to or QString::null if the user wants to + * be the MASTER + **/ + QString host() const; + + /** + * @param port The port that will be shown by default + **/ + void setPort(unsigned short int port); + + /** + * @return The port to connect to / to listen + **/ + unsigned short int port() const; + + /** + * Specifies which state is the default (0 = server game; 1 = join game) + * @param state The default state. 0 For a server game, 1 to join a game + **/ + void setDefault(int state); + + /** + * Sets DNS-SD service type, both for publishing and browsing + * @param type Service type (something like _kwin4._tcp). + * It should be unique for application. + * @since 3.4 + **/ + void setType(const QString& type); + + /** + * @return service type + */ + QString type() const; + + /** + * Set game name for publishing. + * @param name Game name. Important only for server mode. If not + * set hostname will be used. In case of name conflict -2, -3 and so on will be added to name. + */ + void setName(const QString& name); + + /** + * @return game name. + */ + QString gameName() const; + +protected slots: + /** + * The type has changed, ie the user switched between creating or + * joining. + **/ + void slotTypeChanged(int); + void slotGamesFound(); + void slotGameSelected(int); + +signals: + void signalNetworkSetup(); + void signalServerTypeChanged(int); + +private: + void showDnssdControls(); + KGameConnectWidgetPrivate* d; + +}; + +/** + * @short Dialog to ask for host and port + * + * This Dialog is used to create a game. You call initConnection(port, + * QString::null, parent, true) to create a network game (as a server) + * or initConnection(port, host, parent) to join a network game. + * + * @author Andreas Beckermann <[email protected]> + **/ +class KGameConnectDialog : public KDialogBase +{ + Q_OBJECT +public: + KGameConnectDialog(QWidget* parent = 0,int buttonmask=Ok|Cancel); + virtual ~KGameConnectDialog(); + + /** + * Shows a dialog to either connect to an existing game or to create a + * server game, depending on user's choice. + * @param port The port the user wants to connect to. + * @param host The host the user wants to connect to. Will be + * QString::null if server game is chosen + * @param parent The parent of the dialog + * @param server True to create a network game per default, false to + * join a game by default + **/ + static int initConnection(unsigned short int& port, QString& host, QWidget* parent, bool server = false); + + /** + * @param host The host to connect to by default + **/ + void setHost(const QString& host); + + /** + * @return The host to connect to or QString::null if the user wants to + * be the MASTER + **/ + QString host() const; + + /** + * @param port The port that will be shown by default + **/ + void setPort(unsigned short int port); + + /** + * @return The port to connect to / to listen + **/ + unsigned short int port() const; + + /** + * Specifies which state is the default (0 = server game; 1 = join game) + * @param state The default state. 0 For a server game, 1 to join a game + **/ + void setDefault(int state); + +signals: + void signalNetworkSetup(); + +private: + KGameConnectDialogPrivate* d; +}; + +#endif diff --git a/libkdegames/kgame/dialogs/kgamedebugdialog.cpp b/libkdegames/kgame/dialogs/kgamedebugdialog.cpp new file mode 100644 index 00000000..b112b04c --- /dev/null +++ b/libkdegames/kgame/dialogs/kgamedebugdialog.cpp @@ -0,0 +1,548 @@ +/* + This file is part of the KDE games library + Copyright (C) 2001 Andreas Beckermann ([email protected]) + Copyright (C) 2001 Martin Heni ([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. +*/ + +#include "kgamedebugdialog.h" + +#include "kgamemessage.h" +#include "kgame.h" +#include "kplayer.h" +#include "kgamepropertyhandler.h" + +#include <klistview.h> +#include <klistbox.h> +#include <klocale.h> +#include <kdebug.h> +#include <kpushbutton.h> +#include <kstdguiitem.h> + +#include <qlayout.h> +#include <qstring.h> +#include <qintdict.h> +#include <qlabel.h> +#include <qdatetime.h> + +#include <typeinfo> + + +class KGameDebugDialogPrivate +{ +public: + KGameDebugDialogPrivate() + { + mGame = 0; + + mGamePage = 0; + mGameProperties = 0; + mGameAddress = 0; + mGameId = 0; + mGameCookie = 0; + mGameMaster = 0; + mGameAdmin = 0; + mGameOffering = 0; + mGameStatus = 0; + mGameRunning = 0; + mGameMaxPlayers = 0; + mGameMinPlayers = 0; + mGamePlayerCount = 0; + + mPlayerPage = 0; + mPlayerList = 0; + mPlayerProperties = 0; + mPlayerAddress = 0; + mPlayerId = 0; + mPlayerName = 0; + mPlayerGroup = 0; + mPlayerUserId = 0; + mPlayerMyTurn = 0; + mPlayerAsyncInput= 0; + mPlayerKGameAddress = 0; + mPlayerVirtual = 0; + mPlayerActive = 0; + mPlayerRtti = 0; + mPlayerNetworkPriority = 0; + + mMessagePage = 0; + mMessageList = 0; + mHideIdList = 0; + } + + const KGame* mGame; + + QFrame* mGamePage; + KListView* mGameProperties; + QListViewItem* mGameAddress; + QListViewItem* mGameId; + QListViewItem* mGameCookie; + QListViewItem* mGameMaster; + QListViewItem* mGameAdmin; + QListViewItem* mGameOffering; + QListViewItem* mGameStatus; + QListViewItem* mGameRunning; + QListViewItem* mGameMaxPlayers; + QListViewItem* mGameMinPlayers; + QListViewItem* mGamePlayerCount; + + QFrame* mPlayerPage; + KListBox* mPlayerList; + KListView* mPlayerProperties; + QListViewItem* mPlayerAddress; + QListViewItem* mPlayerId; + QListViewItem* mPlayerName; + QListViewItem* mPlayerGroup; + QListViewItem* mPlayerUserId; + QListViewItem* mPlayerMyTurn; + QListViewItem* mPlayerAsyncInput; + QListViewItem* mPlayerKGameAddress; + QListViewItem* mPlayerVirtual; + QListViewItem* mPlayerActive; + QListViewItem* mPlayerRtti; + QListViewItem* mPlayerNetworkPriority; + + QFrame* mMessagePage; + KListView* mMessageList; + KListBox* mHideIdList; +}; + +KGameDebugDialog::KGameDebugDialog(KGame* g, QWidget* parent, bool modal) : + KDialogBase(Tabbed, i18n("KGame Debug Dialog"), Close, Close, + parent, 0, modal, true) +{ + d = new KGameDebugDialogPrivate; + + initGamePage(); + initPlayerPage(); + initMessagePage(); + + setKGame(g); +} + +KGameDebugDialog::~KGameDebugDialog() +{ + delete d; +} + +void KGameDebugDialog::initGamePage() +{ + d->mGamePage = addPage(i18n("Debug &KGame")); + QVBoxLayout* topLayout = new QVBoxLayout(d->mGamePage, marginHint(), spacingHint()); + QHBoxLayout* layout = new QHBoxLayout(topLayout); + + KListView* v = new KListView(d->mGamePage); + v->addColumn(i18n("Data")); + v->addColumn(i18n("Value")); + layout->addWidget(v); + + d->mGameProperties = new KListView(d->mGamePage); + d->mGameProperties->addColumn(i18n("Property")); + d->mGameProperties->addColumn(i18n("Value")); + d->mGameProperties->addColumn(i18n("Policy")); + layout->addWidget(d->mGameProperties); + + QPushButton* b = new QPushButton(i18n("Update"), d->mGamePage); + connect(b, SIGNAL(pressed()), this, SLOT(slotUpdateGameData())); + topLayout->addWidget(b); + +// game data + d->mGameAddress = new QListViewItem(v, i18n("KGame Pointer")); + d->mGameId = new QListViewItem(v, i18n("Game ID")); + d->mGameCookie = new QListViewItem(v, i18n("Game Cookie")); + d->mGameMaster = new QListViewItem(v, i18n("Is Master")); + d->mGameAdmin = new QListViewItem(v, i18n("Is Admin")); + d->mGameOffering = new QListViewItem(v, i18n("Is Offering Connections")); + d->mGameStatus = new QListViewItem(v, i18n("Game Status")); + d->mGameRunning = new QListViewItem(v, i18n("Game is Running")); + d->mGameMaxPlayers = new QListViewItem(v, i18n("Maximal Players")); + d->mGameMinPlayers = new QListViewItem(v, i18n("Minimal Players")); + d->mGamePlayerCount = new QListViewItem(v, i18n("Players")); +} + +void KGameDebugDialog::initPlayerPage() +{ + d->mPlayerPage = addPage(i18n("Debug &Players")); + QVBoxLayout* topLayout = new QVBoxLayout(d->mPlayerPage, marginHint(), spacingHint()); + QHBoxLayout* layout = new QHBoxLayout(topLayout); + + //TODO: connect to the KGame signals for joined/removed players!!! + QVBoxLayout* listLayout = new QVBoxLayout(layout); + QLabel* listLabel = new QLabel(i18n("Available Players"), d->mPlayerPage); + listLayout->addWidget(listLabel); + d->mPlayerList = new KListBox(d->mPlayerPage); + connect(d->mPlayerList, SIGNAL(executed(QListBoxItem*)), this, SLOT(slotUpdatePlayerData(QListBoxItem*))); + listLayout->addWidget(d->mPlayerList); + d->mPlayerList->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding)); + + KListView* v = new KListView(d->mPlayerPage); + layout->addWidget(v); + v->addColumn(i18n("Data")); + v->addColumn(i18n("Value")); + + d->mPlayerProperties = new KListView(d->mPlayerPage); + d->mPlayerProperties->addColumn(i18n("Property")); + d->mPlayerProperties->addColumn(i18n("Value")); + d->mPlayerProperties->addColumn(i18n("Policy")); + layout->addWidget(d->mPlayerProperties); + + QPushButton* b = new QPushButton(i18n("Update"), d->mPlayerPage); + connect(b, SIGNAL(pressed()), this, SLOT(slotUpdatePlayerList())); + topLayout->addWidget(b); + + d->mPlayerAddress = new QListViewItem(v, i18n("Player Pointer")); + d->mPlayerId = new QListViewItem(v, i18n("Player ID")); + d->mPlayerName = new QListViewItem(v, i18n("Player Name")); + d->mPlayerGroup = new QListViewItem(v, i18n("Player Group")); + d->mPlayerUserId = new QListViewItem(v, i18n("Player User ID")); + d->mPlayerMyTurn = new QListViewItem(v, i18n("My Turn")); + d->mPlayerAsyncInput = new QListViewItem(v, i18n("Async Input")); + d->mPlayerKGameAddress = new QListViewItem(v, i18n("KGame Address")); + d->mPlayerVirtual = new QListViewItem(v, i18n("Player is Virtual")); + d->mPlayerActive = new QListViewItem(v, i18n("Player is Active")); + d->mPlayerRtti = new QListViewItem(v, i18n("RTTI")); + d->mPlayerNetworkPriority = new QListViewItem(v, i18n("Network Priority")); +} + +void KGameDebugDialog::initMessagePage() +{ + d->mMessagePage = addPage(i18n("Debug &Messages")); + QGridLayout* layout = new QGridLayout(d->mMessagePage, 11, 7, marginHint(), spacingHint()); + d->mMessageList = new KListView(d->mMessagePage); + layout->addMultiCellWidget(d->mMessageList, 0, 9, 0, 3); + d->mMessageList->addColumn(i18n("Time")); + d->mMessageList->addColumn(i18n("ID")); + d->mMessageList->addColumn(i18n("Receiver")); + d->mMessageList->addColumn(i18n("Sender")); + d->mMessageList->addColumn(i18n("ID - Text")); + + QPushButton* hide = new QPushButton(i18n("&>>"), d->mMessagePage); + connect(hide, SIGNAL(pressed()), this, SLOT(slotHideId())); + layout->addWidget(hide, 4, 4); + + QPushButton* show = new QPushButton(i18n("&<<"), d->mMessagePage); + connect(show, SIGNAL(pressed()), this, SLOT(slotShowId())); + layout->addWidget(show, 6, 4); + + QLabel* l = new QLabel(i18n("Do not show IDs:"), d->mMessagePage); + layout->addMultiCellWidget(l, 0, 0, 5, 6); + d->mHideIdList = new KListBox(d->mMessagePage); + layout->addMultiCellWidget(d->mHideIdList, 1, 8, 5, 6); + + QPushButton* clear = new KPushButton(KStdGuiItem::clear(), d->mMessagePage); + connect(clear, SIGNAL(pressed()), this, SLOT(slotClearMessages())); + layout->addMultiCellWidget(clear, 10, 10, 0, 6); + //TODO: "show all but..." and "show nothing but..." +} + +void KGameDebugDialog::clearPlayerData() +{ + d->mPlayerAddress->setText(1, ""); + d->mPlayerId->setText(1, ""); + d->mPlayerName->setText(1, ""); + d->mPlayerGroup->setText(1, ""); + d->mPlayerUserId->setText(1, ""); + d->mPlayerMyTurn->setText(1, ""); + d->mPlayerAsyncInput->setText(1, ""); + d->mPlayerKGameAddress->setText(1, ""); + d->mPlayerVirtual->setText(1, ""); + d->mPlayerActive->setText(1, ""); + d->mPlayerRtti->setText(1, ""); + d->mPlayerNetworkPriority->setText(1, ""); + + d->mPlayerProperties->clear(); +} + +void KGameDebugDialog::clearGameData() +{ + d->mGameAddress->setText(1, ""); + d->mGameId->setText(1, ""); + d->mGameCookie->setText(1, ""); + d->mGameMaster->setText(1, ""); + d->mGameAdmin->setText(1, ""); + d->mGameOffering->setText(1, ""); + d->mGameStatus->setText(1, ""); + d->mGameRunning->setText(1, ""); + d->mGameMaxPlayers->setText(1, ""); + d->mGameMinPlayers->setText(1, ""); + + d->mGameProperties->clear(); +} + +void KGameDebugDialog::slotUpdatePlayerData() +{ + if (!d->mGame || d->mPlayerList->currentItem() == -1) { + return; + } + slotUpdatePlayerData(d->mPlayerList->item(d->mPlayerList->currentItem())); +} + +void KGameDebugDialog::slotUpdatePlayerList() +{ + QListBoxItem* i = d->mPlayerList->firstItem(); + for (; i; i = d->mPlayerList->firstItem()) { + removePlayer(i); + } + + QPtrList<KPlayer> list = *d->mGame->playerList(); + for (KPlayer* p = list.first(); p; p = list.next()) { + addPlayer(p); + } +} + +void KGameDebugDialog::slotUpdateGameData() +{ + if (!d->mGame) { + d->mGameAddress->setText(1, i18n("NULL pointer")); + return; +} + + clearGameData(); + + QString buf; + buf.sprintf("%p", d->mGame); + d->mGameAddress->setText(1, buf); + d->mGameId->setText(1, QString::number(d->mGame->gameId())); + d->mGameCookie->setText(1, QString::number(d->mGame->cookie())); + d->mGameMaster->setText(1, d->mGame->isMaster() ? i18n("True") : i18n("False")); + d->mGameAdmin->setText(1, d->mGame->isAdmin() ? i18n("True") : i18n("False")); + d->mGameOffering->setText(1, d->mGame->isOfferingConnections() ? i18n("True") : i18n("False")); + d->mGameStatus->setText(1, QString::number(d->mGame->gameStatus())); + d->mGameRunning->setText(1, d->mGame->isRunning() ? i18n("True") : i18n("False")); + d->mGameMaxPlayers->setText(1, QString::number(d->mGame->maxPlayers())); + d->mGameMinPlayers->setText(1, QString::number(d->mGame->minPlayers())); + d->mGamePlayerCount->setText(1, QString::number(d->mGame->playerCount())); + +//TODO ios + + KGamePropertyHandler* handler = d->mGame->dataHandler(); + QIntDictIterator<KGamePropertyBase> it(handler->dict()); + while (it.current()) { + QString policy; + switch (it.current()->policy()) { + case KGamePropertyBase::PolicyClean: + policy = i18n("Clean"); + break; + case KGamePropertyBase::PolicyDirty: + policy = i18n("Dirty"); + break; + case KGamePropertyBase::PolicyLocal: + policy = i18n("Local"); + break; + case KGamePropertyBase::PolicyUndefined: + default: + policy = i18n("Undefined"); + break; + } + (void) new QListViewItem(d->mGameProperties, + handler->propertyName(it.current()->id()), + handler->propertyValue(it.current()), + policy); +// kdDebug(11001) << k_funcinfo << ": checking for all game properties: found property name " << name << endl; + ++it; + } +} + +void KGameDebugDialog::slotUpdatePlayerData(QListBoxItem* item) +{ + if (!item || !d->mGame) { + return; + } + + KPlayer* p = d->mGame->findPlayer(item->text().toInt()); + + if (!p) { + kdError(11001) << k_funcinfo << ": cannot find player" << endl; + return; + } + + clearPlayerData(); + + QString buf; + buf.sprintf("%p", p); + d->mPlayerAddress->setText(1, buf); + d->mPlayerId->setText(1, QString::number(p->id())); + d->mPlayerName->setText(1, p->name()); + d->mPlayerGroup->setText(1, p->group()); + d->mPlayerUserId->setText(1, QString::number(p->userId())); + d->mPlayerMyTurn->setText(1, p->myTurn() ? i18n("True") : i18n("False")); + d->mPlayerAsyncInput->setText(1, p->asyncInput() ? i18n("True") : i18n("False")); + buf.sprintf("%p", p->game()); + d->mPlayerKGameAddress->setText(1, buf); + d->mPlayerVirtual->setText(1, p->isVirtual() ? i18n("True") : i18n("False")); + d->mPlayerActive->setText(1, p->isActive() ? i18n("True") : i18n("False")); + d->mPlayerRtti->setText(1, QString::number(p->rtti())); + d->mPlayerNetworkPriority->setText(1, QString::number(p->networkPriority())); + +//TODO ios + +// Properties + KGamePropertyHandler * handler = p->dataHandler(); + QIntDictIterator<KGamePropertyBase> it((handler->dict())); + while (it.current()) { + QString policy; + switch (it.current()->policy()) { + case KGamePropertyBase::PolicyClean: + policy = i18n("Clean"); + break; + case KGamePropertyBase::PolicyDirty: + policy = i18n("Dirty"); + break; + case KGamePropertyBase::PolicyLocal: + policy = i18n("Local"); + break; + case KGamePropertyBase::PolicyUndefined: + default: + policy = i18n("Undefined"); + break; + } + (void)new QListViewItem(d->mPlayerProperties, + handler->propertyName(it.current()->id()), + handler->propertyValue(it.current()), + policy); + ++it; + } +} + +void KGameDebugDialog::clearPages() +{ + clearPlayerData(); + clearGameData(); + d->mPlayerList->clear(); + slotClearMessages(); +} + +void KGameDebugDialog::setKGame(const KGame* g) +{ + slotUnsetKGame(); + d->mGame = g; + if (g) { + //TODO: connect to the KGame signals for joined/removed players!!! + connect(d->mGame, SIGNAL(destroyed()), this, SLOT(slotUnsetKGame())); +// connect(); + + QPtrList<KPlayer> list = *d->mGame->playerList(); + for (KPlayer* p = list.first(); p; p = list.next()) { + addPlayer(p); + } + + slotUpdateGameData(); + + connect(d->mGame, SIGNAL(signalMessageUpdate(int, Q_UINT32, Q_UINT32)), this, SLOT(slotMessageUpdate(int, Q_UINT32, Q_UINT32))); + } +} + +void KGameDebugDialog::slotUnsetKGame() +{ + if (d->mGame) { + disconnect(d->mGame, 0, this, 0); + } + d->mGame = 0; + clearPages(); +} + +void KGameDebugDialog::addPlayer(KPlayer* p) +{ + if (!p) { + kdError(11001) << "trying to add NULL player" << endl; + return; + } + + (void) new QListBoxText(d->mPlayerList, QString::number(p->id())); + //TODO connect to signals, like deleted/removed, ... +} + +void KGameDebugDialog::removePlayer(QListBoxItem* i) +{ + if (!i || !d->mGame) { + return; + } + KPlayer* p = d->mGame->findPlayer(i->text().toInt()); + if (!p) { + return; + } + disconnect(p, 0, this, 0); + if (i->isSelected()) { + clearPlayerData(); + } + delete i; +} + +void KGameDebugDialog::slotMessageUpdate(int msgid, Q_UINT32 receiver, Q_UINT32 sender) +{ + if (!showId(msgid)) { + return; + } + QString msgidText = KGameMessage::messageId2Text(msgid); + if (msgidText.isNull()) { + if (msgid > KGameMessage::IdUser) { + emit signalRequestIdName(msgid-KGameMessage::IdUser, true, msgidText); + } else { + emit signalRequestIdName(msgid, false, msgidText); + } + if (msgidText.isNull()) { + msgidText = i18n("Unknown"); + } + } + (void) new QListViewItem( d->mMessageList, QTime::currentTime().toString(), + QString::number(msgid), QString::number(receiver), + QString::number(sender), msgidText); +} + +void KGameDebugDialog::slotClearMessages() +{ + d->mMessageList->clear(); +} + +void KGameDebugDialog::slotShowId() +{ +/* QListBoxItem* i = d->mHideIdList->firstItem(); + for (; i; i = i->next()) { + if (i->selected()) { + d->mHideIdList->removeItem(i->); + } + }*/ + if (!d->mHideIdList->currentItem()) { + return; + } + d->mHideIdList->removeItem(d->mHideIdList->currentItem()); +} + +void KGameDebugDialog::slotHideId() +{ + if (!d->mMessageList->currentItem()) { + return; + } + int msgid = d->mMessageList->currentItem()->text(1).toInt(); + if (!showId(msgid)) { + return; + } + (void)new QListBoxText(d->mHideIdList, QString::number(msgid)); +} + +bool KGameDebugDialog::showId(int msgid) +{ + QListBoxItem* i = d->mHideIdList->firstItem(); + for (; i; i = i->next()) { + if (i->text().toInt() == msgid) { + return false; + } + } + return true; +} + + +#include "kgamedebugdialog.moc" diff --git a/libkdegames/kgame/dialogs/kgamedebugdialog.h b/libkdegames/kgame/dialogs/kgamedebugdialog.h new file mode 100644 index 00000000..65afc92a --- /dev/null +++ b/libkdegames/kgame/dialogs/kgamedebugdialog.h @@ -0,0 +1,149 @@ +/* + This file is part of the KDE games library + Copyright (C) 2001 Andreas Beckermann ([email protected]) + Copyright (C) 2001 Martin Heni ([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. +*/ + +#ifndef __KGAMEDEBUGDIALOG_H__ +#define __KGAMEDEBUGDIALOG_H__ + +#include <kdialogbase.h> +#include <kdemacros.h> + +class KGame; +class KGameIO; +class KPlayer; +class KGamePropertyBase; + +class KGameDebugDialogPrivate; + +class KDE_EXPORT KGameDebugDialog : public KDialogBase +{ + Q_OBJECT +public: + KGameDebugDialog(KGame* g, QWidget* parent, bool modal = false); + ~KGameDebugDialog(); + + /** + * Automatically connects the KGame object to all error dependant slots. + * Create a KGameErrorDialog object, call this function and forget + * everything. + * @param g The KGame which will emit the erorrs (or not ;-) ) + **/ + void setKGame(const KGame* g); + +public slots: + /** + * Unsets a @ref KGame which has been set using @ref setKGame before. + * This is called automatically when the @ref KGame object is destroyed + * and you normally don't have to call this yourself. + * + * Note that @ref setKGame also unsets an already existing @ref KGame + * object if exising. + **/ + void slotUnsetKGame(); + + /** + * Update the data of the @ref KGame object + **/ + void slotUpdateGameData(); + + /** + * Update the properties of the currently selected player + **/ + void slotUpdatePlayerData(); + + /** + * Updates the list of players and calls @ref clearPlayerData. Note that + * after this call NO player is selected anymore. + **/ + void slotUpdatePlayerList(); + + void slotClearMessages(); + +signals: + /** + * This signal is emitted when the "debug messages" page couldn't find + * the name of a message id. This is usually the case for user-defined + * messages. KGameDebugDialog asks you to give the msgid a name. + * @param messageid The ID of the message. As given to @ref + * KGame::sendMessage + * @param userid User defined msgIds are internally increased by + * @ref KGameMessage::IdUser. You don't have to care about this but if + * this signal is emitted with userid=false (shouldn't happen) then the + * name of an internal message as defined in @ref + * KGameMessage::GameMessageIds couldn't be found. + * @param name The name of the msgid. You have to fill this! + **/ + void signalRequestIdName(int messageid, bool userid, QString& name); + +protected: + void clearPages(); + + /** + * Clear the data of the player view. Note that the player list is NOT + * cleared. + **/ + void clearPlayerData(); + + /** + * Clear the data view of the @ref KGame object + **/ + void clearGameData(); + + /** + * Add a new player to the player list + **/ + void addPlayer(KPlayer* p); + + /** + * Remove a player from the list + **/ + void removePlayer(QListBoxItem* item); + + /** + * @return Whether messages with this msgid shall be displayed or not + **/ + bool showId(int msgid); + +protected slots: + /** + * Update the data of the player specified in item + * @param item The @ref QListBoxItem of the player to be updated. Note + * that the text of this item MUST be the ID of the player + **/ + void slotUpdatePlayerData(QListBoxItem* item); + + void slotShowId(); + void slotHideId(); + + /** + * A message has been received - see @ref KGame::signalMessageUpdate + **/ + void slotMessageUpdate(int msgid, Q_UINT32 receiver, Q_UINT32 sender); + +private: + void initGamePage(); + void initPlayerPage(); + void initMessagePage(); + +private: + KGameDebugDialogPrivate* d; +}; + + +#endif diff --git a/libkdegames/kgame/dialogs/kgamedialog.cpp b/libkdegames/kgame/dialogs/kgamedialog.cpp new file mode 100644 index 00000000..dc564b8e --- /dev/null +++ b/libkdegames/kgame/dialogs/kgamedialog.cpp @@ -0,0 +1,347 @@ +/* + This file is part of the KDE games library + Copyright (C) 2001 Andreas Beckermann ([email protected]) + Copyright (C) 2001 Martin Heni ([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. +*/ + +#include <qlayout.h> +#include <qvbox.h> + +#include <klocale.h> + +#include "kgame.h" +#include "kplayer.h" +#include "kgamedialogconfig.h" + +#include "kgamedialog.h" + +#include "kgamedialog.moc" + +class KGameDialogPrivate +{ +public: + KGameDialogPrivate() + { + mGamePage = 0; + mNetworkPage = 0; + mMsgServerPage = 0; + mTopLayout = 0; + + mNetworkConfig = 0; + mGameConfig = 0; + + mOwner = 0; + mGame = 0; + } + + QVBox* mGamePage; + QVBox* mNetworkPage; + QVBox* mMsgServerPage;// unused here? + QVBoxLayout* mTopLayout; + KGameDialogNetworkConfig* mNetworkConfig; + KGameDialogGeneralConfig* mGameConfig; + +// a list of all config widgets added to this dialog + QPtrList<KGameDialogConfig> mConfigWidgets; + +// just pointers: + KPlayer* mOwner; + KGame* mGame; +}; + +KGameDialog::KGameDialog(KGame* g, KPlayer* owner, const QString& title, + QWidget* parent, bool modal) + : KDialogBase(Tabbed, title, Ok|Default|Apply, + Ok, parent, 0, modal, true) +{ + init(g, owner); +} + +KGameDialog::KGameDialog(KGame* g, KPlayer* owner, const QString& title, + QWidget* parent, long initConfigs, int chatMsgId, bool modal) + : KDialogBase(Tabbed, title, Ok|Default|Apply, + Ok, parent, 0, modal, true) +{ + init(g, owner); + if ((ConfigOptions)initConfigs!=NoConfig) { + initDefaultDialog((ConfigOptions)initConfigs, chatMsgId); + } +} + +void KGameDialog::init(KGame* g, KPlayer* owner) +{ +//AB: do we need a "Cancel" Button? currently removed + +// kdDebug(11001) << k_funcinfo << ": this=" << this << endl; + d = new KGameDialogPrivate; + + setOwner(owner); + setKGame(g); + if (g) { + setAdmin(g->isAdmin()); + } else { + setAdmin(false); + } +} + +void KGameDialog::initDefaultDialog(ConfigOptions initConfigs, int chatMsgId) +{ + if (initConfigs & GameConfig) { + kdDebug() << "add gameconf" << endl; + addGameConfig(new KGameDialogGeneralConfig(0)); + } + if (initConfigs & NetworkConfig) { + addNetworkConfig(new KGameDialogNetworkConfig(0)); + } + if (initConfigs & (MsgServerConfig) ) { + addMsgServerConfig(new KGameDialogMsgServerConfig(0)); + } + if (initConfigs & ChatConfig) { + KGameDialogChatConfig * c = new KGameDialogChatConfig(chatMsgId, 0); + if (d->mGamePage) { + addChatWidget(c, d->mGamePage); + } else { + addConfigPage(c, i18n("&Chat")); + } + } + if (initConfigs & BanPlayerConfig) { + // add the connection management system - ie the widget where the ADMIN can + // kick players out + if (d->mNetworkPage) { + // put it on the network page + addConnectionList(new KGameDialogConnectionConfig(0), d->mNetworkPage); + } else { + // if no network page available put it on an own page + addConfigPage(new KGameDialogConnectionConfig(0), i18n("C&onnections")); + } + } +} + +KGameDialog::~KGameDialog() +{ +// kdDebug(11001) << "DESTRUCT KGameDialog" << this << endl; + d->mConfigWidgets.setAutoDelete(true); + d->mConfigWidgets.clear(); + delete d; +} + +void KGameDialog::addGameConfig(KGameDialogGeneralConfig* conf) +{ + if (!conf) { + return; + } + d->mGameConfig = conf; + d->mGamePage = addConfigPage(d->mGameConfig, i18n("&Game")); +} + +void KGameDialog::addNetworkConfig(KGameDialogNetworkConfig* netConf) +{ + if (!netConf) { + return; + } + d->mNetworkConfig = netConf; + d->mNetworkPage = addConfigPage(netConf, i18n("&Network")); +} + +void KGameDialog::addMsgServerConfig(KGameDialogMsgServerConfig* msgConf) +{ + if (!msgConf) { + return; + } + d->mMsgServerPage = addConfigPage(msgConf, i18n("&Message Server")); +} + +void KGameDialog::addChatWidget(KGameDialogChatConfig* chat, QVBox* parent) +{ + if (!chat) { + return; + } + if (!parent) { + parent = d->mGamePage; + } + if (!parent) { + kdError(11001) << "cannot add chat widget without page" << endl; + return; + } + addConfigWidget(chat, parent); +} + +void KGameDialog::addConnectionList(KGameDialogConnectionConfig* c, QVBox* parent) +{ + if (!c) { + return; + } + if (!parent) { + parent = d->mNetworkPage; + } + if (!parent) { + kdError(11001) << "Cannot add connection list without page" << endl; + return; + } + addConfigWidget(c, parent); +} + +QVBox *KGameDialog::configPage(ConfigOptions which) +{ + QVBox *box = 0; + switch(which) + { + case NetworkConfig: + box = d->mNetworkPage; + break; + case GameConfig: + box = d->mGamePage; + break; + case MsgServerConfig: + box = d->mMsgServerPage; + break; + default: + kdError(11001) << k_funcinfo << ": Parameter " << which << " not supported" << endl; + } + return box; +} + +QVBox* KGameDialog::addConfigPage(KGameDialogConfig* widget, const QString& title) +{ + if (!widget) { + kdError(11001) << "Cannot add NULL config widget" << endl; + return 0; + } + QVBox* page = addVBoxPage(title); + addConfigWidget(widget, page); + return page; +} + +void KGameDialog::addConfigWidget(KGameDialogConfig* widget, QWidget* parent) +{ + if (!widget) { + kdError(11001) << "Cannot add NULL config widget" << endl; + return; + } + if (!parent) { + kdError(11001) << "Cannot reparent to NULL widget" << endl; + return; + } +// kdDebug(11001) << "reparenting widget" << endl; + widget->reparent(parent, QPoint(0,0)); + d->mConfigWidgets.append(widget); + connect(widget, SIGNAL(destroyed(QObject*)), this, SLOT(slotRemoveConfigWidget(QObject*))); + if (!d->mGame) { + kdWarning(11001) << "No game has been set!" << endl; + } else { + widget->setKGame(d->mGame); + widget->setAdmin(d->mGame->isAdmin()); + } + if (!d->mOwner) { + kdWarning(11001) << "No player has been set!" << endl; + } else { + widget->setOwner(d->mOwner); + } + widget->show(); +} + +KGameDialogGeneralConfig* KGameDialog::gameConfig() const +{ return d->mGameConfig; } +KGameDialogNetworkConfig* KGameDialog::networkConfig() const +{ return d->mNetworkConfig; } + +void KGameDialog::slotApply() +{ + submitToKGame(); +} + +void KGameDialog::slotDefault() +{ + if (!d->mGame) { + return; + } + +//TODO *only* call setKGame/setOwner for the *current* page!! + setKGame(d->mGame); + setOwner(d->mOwner); +} + +void KGameDialog::slotOk() +{ + slotApply(); + QDialog::accept(); +} + +void KGameDialog::setOwner(KPlayer* owner) +{ +//AB: note: NULL player is ok! + d->mOwner = owner; + for (int unsigned i = 0; i < d->mConfigWidgets.count(); i++) { + if (d->mConfigWidgets.at(i)) { + d->mConfigWidgets.at(i)->setOwner(d->mOwner); + //TODO: hide playerName in KGameDialogGeneralConfig + } else { + kdError(11001) << "NULL widget??" << endl; + } + } +} + +void KGameDialog::setKGame(KGame* g) +{ + if (d->mGame) { + disconnect(d->mGame, 0, this, 0); + } + d->mGame = g; + for (int unsigned i = 0; i < d->mConfigWidgets.count(); i++) { + d->mConfigWidgets.at(i)->setKGame(d->mGame); + } + if (d->mGame) { + setAdmin(d->mGame->isAdmin()); + connect(d->mGame, SIGNAL(destroyed()), this, SLOT(slotUnsetKGame())); + connect(d->mGame, SIGNAL(signalAdminStatusChanged(bool)), + this, SLOT(setAdmin(bool))); + } +} + +void KGameDialog::setAdmin(bool admin) +{ + for (int unsigned i = 0; i < d->mConfigWidgets.count(); i++) { + d->mConfigWidgets.at(i)->setAdmin(admin); + } +} + +void KGameDialog::slotUnsetKGame() // called when KGame is destroyed +{ setKGame(0); } + +void KGameDialog::submitToKGame() +{ + if (!d->mGame) { + kdError(11001) << k_funcinfo << ": no game has been set" << endl; + return; + } + if (!d->mOwner) { + kdError(11001) << k_funcinfo << ": no player has been set" << endl; + return; + } + + for (int unsigned i = 0; i < d->mConfigWidgets.count(); i++) { +// kdDebug(11001) << "submit to kgame " << i << endl; + d->mConfigWidgets.at(i)->submitToKGame(d->mGame, d->mOwner); +// kdDebug(11001) << "done: submit to kgame " << i << endl; + } +} + +void KGameDialog::slotRemoveConfigWidget(QObject* configWidget) +{ + d->mConfigWidgets.removeRef((KGameDialogConfig*)configWidget); +} + diff --git a/libkdegames/kgame/dialogs/kgamedialog.h b/libkdegames/kgame/dialogs/kgamedialog.h new file mode 100644 index 00000000..f7a0c6e5 --- /dev/null +++ b/libkdegames/kgame/dialogs/kgamedialog.h @@ -0,0 +1,320 @@ +/* + This file is part of the KDE games library + Copyright (C) 2001 Andreas Beckermann ([email protected]) + Copyright (C) 2001 Martin Heni ([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. +*/ + +// NAMING +// please follow these naming rules if you add/change classes: +// the main dialog is named KGameDialog and the base config widget +// KGameDialogConfig. All config widgets are named KGameDialogXYZConfig (where +// XYZ = the name of the config widget, like "general" or "network") and are +// inherited from KGameDialogConfig. + +#ifndef __KGAMEDIALOG_H__ +#define __KGAMEDIALOG_H__ + +#include <kdialogbase.h> +#include <kdemacros.h> +class QGridLayout; +class QVBoxLayout; +class QListBoxItem; + +class KGame; +class KPlayer; +class KGamePropertyBase; + +class KGameDialogConfig; +class KGameDialogGeneralConfig; +class KGameDialogNetworkConfig; +class KGameDialogMsgServerConfig; +class KGameDialogChatConfig; +class KGameDialogConnectionConfig; + +class KGameDialogPrivate; +/** + * TODO: rewrite entire documentation. Nearly nothing is valid anymore. + * The main configuration dialog for KGame. Here all players meat each other, + * every player can see how many players connected (and their names) and the + * ADMIN can even "kick" players out. You can talk to each other (using + * KGameChat and the ADMIN can define the maxPlayers/minPlayers as well as the + * number of computer players. + * + * + * AB: setDefaultXYZ is obsolete!! + * You will usually create an instance of KGameDialog or any derived class and + * call setDefaultXYZ methods. Example (maybe + * obsoleted parameters - docu is currently changing very fast): + * \code + * KGameDialog dlg(kgame, i18n("New Game"), localPlayer, this, true, + * ID_CHAT); + * dlg.setDefaultNetworkInfo(port, host); // AB: obsolete! + * dlg.exec(); + * \endcode + * This will create a default modal dialog with the title "New Game". You don't + * have to do more than this. + * + * @short Main configuration dialog for KGame + * @author Andreas Beckermann <[email protected]> + **/ +class KDE_EXPORT KGameDialog : public KDialogBase +{ + Q_OBJECT +public: + + enum ConfigOptions + { + NoConfig = 0, + ChatConfig = 1, + GameConfig = 2, + NetworkConfig = 4, + MsgServerConfig = 8, + BanPlayerConfig = 16, + AllConfig = 0xffff + }; + + /** + * Create an empty KGameDialog. You can add widgets using + * addConfigPage. + * @param g The KGame object of this game + * @param owner The KPlayer object who is responsible for this + * dialog, aka "the local player" + * @param title The title of the dialog - see KDialog::setCaption + * @param parent The parent of the dialog + * @param modal Whether the dialog is modal or not + **/ + KGameDialog(KGame* g, KPlayer* owner, const QString& title, + QWidget* parent, bool modal = false); + + /** + * Create a KGameDialog with the standard configuration widgets. This + * creates the following widgets: + * <ul> + * <li> KGameDialogGeneralConfig + * <li> KGameDialogNetworkConfig + * <li> KGameDialogMsgServerConfig + * <li> KGameDialogChatConfig + * <li> KGameDialogConnectionConfig + * </ul> + * If you want to use your own implementations (or none) of the widgets + * above you should subclass KGameDialog. Use addGameConfig, + * addNetworkConfig, addMsgConfig, addChatWidget and + * addConnectionList in this case. + * + * If you want to add further configuration widget you can simply use + * addConfigPage + * @param g The KGame object of this game + * @param owner The KPlayer object who is responsible for this + * dialog, aka "the local player" + * @param title The title of the dialog - see KDialog::setCaption + * @param parent The parent of the dialog + * @param modal Whether the dialog is modal or not + * @param initConfigs whether the default KGameDialogConfig widgets + * shall be created using initDefaultDialog. Use false if you want + * to use custom widgets. + * @param chatMsgId The ID of Chat messages. See KGameChat. Unused + * if initConfigs = false + **/ + KGameDialog(KGame* g, KPlayer* owner, const QString& title, + QWidget* parent, long initConfigs = AllConfig, + int chatMsgId = 15432, bool modal = false); + + virtual ~KGameDialog(); + + + /** + * Change the owner of the dialog. This will be used as the fromPlayer in + * KGameChat and will receive the entered player name. + * @param owner The owner of the dialog. It must already be added to the + * KGame object! + * + * Calls the KGameDialogConfig::setOwner implementation of all + * widgets that have been added by addConfigWidget + * @param owner The new owner player of this dialog must already be + * added to the KGame object. Can even be NULL (then no player + * configuration is made) + **/ + void setOwner(KPlayer* owner); + + /** + * Change the KGame object this dialog is used for. + * + * Calls the KGameDialogConfig::setKGame implementation of all + * widgets that have been added by addConfigWidget + * @param g The new KGame object + **/ + void setKGame(KGame* g); + + /** + * This will submit all configuration data to the KGame object. + * Automatically called by slotApply and slotOk + * There is no need to replace this unless you + * want to add widgets which are not derived from those classes + **/ + virtual void submitToKGame(); + + /** + * Adds a KGameChat to the dialog. If no parent is specified the + * game page will be used. + * @param chat The chat widget + * @param parent The parent of the chat widget. This MUST be an + * already added config widget. Note that the game page will be used + * if parent is 0. + **/ + void addChatWidget(KGameDialogChatConfig* chat, QVBox* parent = 0); + + /** + * Add a connection list to the dialog. The list consists of a + * KLisBox containing all players in the current game (see + * KGame::playerList). The admin can "ban" players, ie kick them out of + * the game. + * + * This is another not-really-config-config-widget. It just displays the + * connections and lets you ban players. + * @param c The KGameDialogConnectionConfig object + * @param parent The parent of the widget. If 0 the networkConfig + * page is used. + **/ + void addConnectionList(KGameDialogConnectionConfig* c, QVBox* parent = 0); + + /** + * Add a new page to the dialog. The page will contain you new config + * widget and will have your provided title. + * + * The widget will be reparented to this dialog. This also calls + * KGameDialogConfig::setKGame and KGameDialogConfig::setOwner. + * @param widget The new config widget + * @param title The title of the newly added page. + * @return The newly added page which contains your config widget. + **/ + QVBox* addConfigPage(KGameDialogConfig* widget, const QString& title); + + /** + * @return The QVBox of the given key, The key is from ConfigOptions + * Note that not all are supported yet + **/ + QVBox *configPage(ConfigOptions which); + + /** + * @return The default netowrk config. Note that this always returns 0 if + * you did not specify NetworkConfig in the constructor! + **/ + KGameDialogNetworkConfig* networkConfig() const; + + /** + * @return The default game config. Note that this always returns 0 if + * you did not specify GameConfig in the constructor! + **/ + KGameDialogGeneralConfig* gameConfig() const; + + /** + * Add a config widget to the specified parent. Usually you call + * addConfigPage for one widget and addConfigWidget for another to add + * it to the same page. Just use the returned page of + * addConfigPage. + **/ + void addConfigWidget(KGameDialogConfig* widget, QWidget* parent); + + /** + * Used to add the main network config widget in a new page. Use this to + * make networkConfig return something useful. + **/ + void addNetworkConfig(KGameDialogNetworkConfig* netConf); + + /** + * Add the main game config widget in a new page. Use this to make + * gameConfig return something useful. + **/ + void addGameConfig(KGameDialogGeneralConfig* conf); + + /** + * Used to add the message server config widget in a new page. + **/ + void addMsgServerConfig(KGameDialogMsgServerConfig* conf); + +protected: + + /** + * This is used to create a dialog containing all the default widgets. + * + * You may want to use this if you just want to use your own + * configuration widgets which inherit the standard ones. + * + * Note that if one of the widgets is NULL the default implementation + * will be used! (except the chat widget - you need to create it + * yourself as you have to provide a message id) + * @param initConfigs The widgets to be created + * @param chatMsgId The msgid for the chat config (only if specified in + * initConfigs) - see KGameDialogChatConfig + **/ + void initDefaultDialog(ConfigOptions initConfigs, int chatMsgId = 15432); + + /** + * Go through all config widgets and call their + * KGameDialogConfig::setKGame and KGameDialogConfig::setOwner implementation + * + * This function could be private and probably will be very soon. + * Don't use it yourself + **/ + void configureConfigWidgets(); + +protected slots: + /** + * Called when the user clicks on Ok. Calls slotApply and + * QDialog::accept() + **/ + virtual void slotOk(); + + /** + * Just calls submitToKGame() + **/ + virtual void slotApply(); + + /** + * Sets the default values for the configuration widgets. Set these + * values by (e.g.) setDefaultMaxPlayers() + * @deprecated + **/ + virtual void slotDefault(); + + /** + * Called when the KGame object is destroyed. Calls setKGame(0) so + * that all widgets can disconnect their slots and so on. + **/ + void slotUnsetKGame(); + + /** + * Called when the ADMIN status of this KGame client changes. See + * KGameNetwork::signalAdminStatusChanged + * @param isAdmin TRUE if this client is now the ADMIN otherwise FALSE + **/ + void setAdmin(bool isAdmin); + + /** + * Remove a config widget from the widget list. + * @see QObject::destroyed + **/ + void slotRemoveConfigWidget(QObject* configWidget); + +private: + void init(KGame*, KPlayer*); + +private: + KGameDialogPrivate* d; +}; + +#endif diff --git a/libkdegames/kgame/dialogs/kgamedialogconfig.cpp b/libkdegames/kgame/dialogs/kgamedialogconfig.cpp new file mode 100644 index 00000000..27f91cd1 --- /dev/null +++ b/libkdegames/kgame/dialogs/kgamedialogconfig.cpp @@ -0,0 +1,773 @@ +/* + This file is part of the KDE games library + Copyright (C) 2001 Andreas Beckermann ([email protected]) + Copyright (C) 2001 Martin Heni ([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. +*/ + +#include "kgamedialogconfig.h" + +#include "kgame.h" +#include "kplayer.h" +#include "kgamechat.h" +#include "kgameconnectdialog.h" + +#include <klocale.h> +#include <knuminput.h> +#include <kdialog.h> +#include <klistbox.h> +#include <kmessagebox.h> + +#include <qlayout.h> +#include <qhgroupbox.h> +#include <qlabel.h> +#include <qpushbutton.h> +#include <qlineedit.h> +#include <qvbox.h> +#include <qptrdict.h> + +#include "kgamedialogconfig.moc" + +class KGameDialogConfigPrivate +{ +public: + KGameDialogConfigPrivate() + { + mOwner = 0; + mGame = 0; + + mAdmin = false; + } + + bool mAdmin; + KGame* mGame; + KPlayer* mOwner; +}; + +KGameDialogConfig::KGameDialogConfig(QWidget* parent) : QWidget(parent) +{ + d = new KGameDialogConfigPrivate; +} + +KGameDialogConfig::~KGameDialogConfig() +{ + kdDebug(11001) << k_funcinfo << endl; + delete d; +} + +void KGameDialogConfig::setKGame(KGame* g) +{ + d->mGame = g; +} + +void KGameDialogConfig::setOwner(KPlayer* p) +{ + d->mOwner = p; +} + +void KGameDialogConfig::setAdmin(bool a) +{ + d->mAdmin = a; +} + +KGame* KGameDialogConfig::game() const +{ return d->mGame; } +bool KGameDialogConfig::admin() const +{ return d->mAdmin; } +KPlayer* KGameDialogConfig::owner() const +{ return d->mOwner; } + +/////////////////////////// KGameDialogNetworkConfig ///////////////////////// +class KGameDialogNetworkConfigPrivate +{ +public: + KGameDialogNetworkConfigPrivate() + { + mInitConnection = 0; + mNetworkLabel = 0; + mDisconnectButton = 0; + mConnect = 0; + mDefaultServer=true; + + } + + // QPushButton* mInitConnection; + QHGroupBox* mInitConnection; + QLabel* mNetworkLabel; + QPushButton *mDisconnectButton; + + bool mDefaultServer; + QString mDefaultHost; + unsigned short int mDefaultPort; + KGameConnectWidget *mConnect; +}; + + +KGameDialogNetworkConfig::KGameDialogNetworkConfig(QWidget* parent) + : KGameDialogConfig(parent) +{ +// kdDebug(11001) << k_funcinfo << ": this=" << this << endl; + d = new KGameDialogNetworkConfigPrivate(); + + QVBoxLayout* topLayout = new QVBoxLayout(this, KDialog::marginHint(), KDialog::spacingHint(), "toplayout"); + + QHBoxLayout *hb = new QHBoxLayout(topLayout, KDialog::spacingHint()); + + d->mNetworkLabel = new QLabel(this); + hb->addWidget(d->mNetworkLabel); + + d->mDisconnectButton=new QPushButton(i18n("Disconnect"),this); + connect(d->mDisconnectButton, SIGNAL(clicked()), this, SLOT(slotExitConnection())); + hb->addWidget(d->mDisconnectButton); + + d->mInitConnection = new QHGroupBox(i18n("Network Configuration"), this); + topLayout->addWidget(d->mInitConnection); + + d->mConnect = new KGameConnectWidget(d->mInitConnection); + connect(d->mConnect, SIGNAL(signalNetworkSetup()), this, SLOT(slotInitConnection())); + connect(d->mConnect, SIGNAL(signalServerTypeChanged(int)), + this, SIGNAL(signalServerTypeChanged(int))); + + // Needs to be AFTER the creation of the dialogs + setConnected(false); + setDefaultNetworkInfo("localhost", 7654,true); +} + +KGameDialogNetworkConfig::~KGameDialogNetworkConfig() +{ + kdDebug(11001) << k_funcinfo << endl; + delete d; +} + +void KGameDialogNetworkConfig::slotExitConnection() +{ + kdDebug(11001) << k_funcinfo << " !!!!!!!!!!!!!!!!!!!!!!!" << endl; + if (game()) game()->disconnect(); + setConnected(false,false); +} + +void KGameDialogNetworkConfig::slotInitConnection() +{ + kdDebug(11001) << k_funcinfo << endl; + bool connected = false; + bool master = true; + unsigned short int port = d->mConnect->port(); + QString host = d->mConnect->host(); + + if (host.isNull()) { + master = true; + if (game()) { + game()->setDiscoveryInfo(d->mConnect->type(),d->mConnect->gameName()); + connected = game()->offerConnections(port); + } + } else { + master = false; + if (game()) { + connected = game()->connectToServer(host, port); + } + // We need to learn about failed connections + if (game()) { + connect(game(), SIGNAL(signalConnectionBroken()), + this, SLOT(slotConnectionBroken())); + } + } + setConnected(connected, master); +} + +void KGameDialogNetworkConfig::slotConnectionBroken() +{ + kdDebug(11001) << k_funcinfo << endl; + setConnected(false,false); + KMessageBox::error(this, i18n("Cannot connect to the network")); +} + +void KGameDialogNetworkConfig::setConnected(bool connected, bool master) +{ + if (!connected) { + d->mNetworkLabel->setText(i18n("Network status: No Network")); + d->mInitConnection->setEnabled(true); + d->mDisconnectButton->setEnabled(false); + return; + } + if (master) { + d->mNetworkLabel->setText(i18n("Network status: You are MASTER")); + } else { + d->mNetworkLabel->setText(i18n("Network status: You are connected")); + } + d->mInitConnection->setEnabled(false); + d->mDisconnectButton->setEnabled(true); +} + +void KGameDialogNetworkConfig::submitToKGame(KGame* , KPlayer* ) +{ +} + +void KGameDialogNetworkConfig::setKGame(KGame* g) +{ + KGameDialogConfig::setKGame(g); + if (!game()) { + setConnected(false); + return; + } + setConnected(game()->isNetwork(), game()->isMaster()); +} + +void KGameDialogNetworkConfig::setDefaultNetworkInfo(const QString& host, unsigned short int port,bool server) +{ + d->mDefaultPort = port; + d->mDefaultHost = host; + d->mDefaultServer = server; + + d->mConnect->setHost(host); + d->mConnect->setPort(port); + if (server) { + d->mConnect->setDefault(0); + } else { + d->mConnect->setDefault(1); + } +} + +void KGameDialogNetworkConfig::setDiscoveryInfo(const QString& type, const QString& name) +{ + d->mConnect->setType(type); + d->mConnect->setName(name); +} + +/////////////////////////// KGameDialogGeneralConfig ///////////////////////// +class KGameDialogGeneralConfigPrivate +{ +public: + KGameDialogGeneralConfigPrivate() + { + mTopLayout = 0; + mName = 0; + } + + QLineEdit* mName; + + QVBoxLayout* mTopLayout; +}; + +KGameDialogGeneralConfig::KGameDialogGeneralConfig(QWidget* parent, bool initializeGUI) + : KGameDialogConfig(parent) +{ +// kdDebug(11001) << k_funcinfo << ": this=" << this << endl; + d = new KGameDialogGeneralConfigPrivate; + + if (initializeGUI) { + d->mTopLayout = new QVBoxLayout(this, KDialog::marginHint(), KDialog::spacingHint()); + d->mTopLayout->setAutoAdd(true); + + QWidget* nameWidget = new QWidget(this); + QHBoxLayout* l = new QHBoxLayout(nameWidget); + QLabel* nameLabel = new QLabel(i18n("Your name:"), nameWidget); + l->addWidget(nameLabel); + d->mName = new QLineEdit(nameWidget); + l->addWidget(d->mName); + } +} + +KGameDialogGeneralConfig::~KGameDialogGeneralConfig() +{ + kdDebug(11001) << k_funcinfo << endl; + delete d; +} + +void KGameDialogGeneralConfig::setPlayerName(const QString& name) +{ + if (d->mName) { + d->mName->setText(name); + } +} + +QString KGameDialogGeneralConfig::playerName() const +{ + return d->mName ? d->mName->text() : QString::null; +} + +void KGameDialogGeneralConfig::setOwner(KPlayer* p) +{ + if (owner()) { + owner()->disconnect(this); + } + KGameDialogConfig::setOwner(p); + if (!owner()) { + // can this config be used at all? + // maybe call hide() + return; + } + connect(owner(), SIGNAL(signalPropertyChanged(KGamePropertyBase*, KPlayer*)), + this, SLOT(slotPropertyChanged(KGamePropertyBase*, KPlayer*))); + setPlayerName(p->name()); + //TODO: connect signalPropertyChanged and check for playername changes! +} + +void KGameDialogGeneralConfig::setKGame(KGame* g) +{ + KGameDialogConfig::setKGame(g); + if (!g) { + // TODO + // can this config be used at all? + // maybe call hide() + return; + } +} + +void KGameDialogGeneralConfig::setAdmin(bool admin) +{ + KGameDialogConfig::setAdmin(admin); +// enable/disable widgets + +} + +void KGameDialogGeneralConfig::submitToKGame(KGame* g, KPlayer* p) +{ +//FIXME + if (p) { + p->setName(playerName()); + } + if (g) { + } +} + +void KGameDialogGeneralConfig::slotPropertyChanged(KGamePropertyBase* prop, KPlayer* p) +{ + if (!prop || !p || p != owner()) { + return; + } + switch (prop->id()) { + case KGamePropertyBase::IdName: + setPlayerName(p->name()); + break; + default: + break; + } +} + +class KGameDialogMsgServerConfigPrivate +{ +public: + KGameDialogMsgServerConfigPrivate() + { + senderLayout = 0; + localLayout = 0; + + changeMaxClients = 0; + changeAdmin= 0; + removeClient= 0; + noAdmin = 0; + + noMaster = 0; + } + + QVBoxLayout* senderLayout; + QHBoxLayout* localLayout; + + QPushButton* changeMaxClients; + QPushButton* changeAdmin; + QPushButton* removeClient; + QLabel* noAdmin; + + QLabel* noMaster; +}; + + +// TODO: change ADMIN ID, remove CLIENTS, change MAXCLIENTS +// we do everything here with QPushButtons as we want to wait a moment before +// continuing - the message must be sent over network first +KGameDialogMsgServerConfig::KGameDialogMsgServerConfig(QWidget* parent) + : KGameDialogConfig(parent) +{ + d = new KGameDialogMsgServerConfigPrivate; + + QVBoxLayout* topLayout = new QVBoxLayout(this, KDialog::marginHint(), KDialog::spacingHint()); + d->senderLayout = new QVBoxLayout(topLayout); + d->localLayout = new QHBoxLayout(topLayout); +} + +KGameDialogMsgServerConfig::~KGameDialogMsgServerConfig() +{ + kdDebug(11001) << k_funcinfo << endl; + delete d; +} + +void KGameDialogMsgServerConfig::setKGame(KGame* g) +{ + KGameDialogConfig::setKGame(g); + //TODO display the ID of the admin if we aren't + // connect(g, SIGNAL(signalAdminChanged(int)), this, SLOT(slotChangeIsAdmin(int)));//TODO + if (!game()) { + // we cannot do anything without a KGame object! + setAdmin(false); + return; + } + setAdmin(game()->isAdmin()); + setHasMsgServer(game()->messageServer()); +} + + +void KGameDialogMsgServerConfig::slotChangeMaxClients() +{ + if (!game()) { + kdError(11001) << k_funcinfo << ": no valid game object available!" << endl; + return; + } + if (!game()->isAdmin()) { + kdError(11001) << k_funcinfo << ": only ADMIN is allowed to call this!" << endl; + return; + } + int max; +// edit->setText(QString::number()); // current max clients! //TODO + + QDialog* dialog = new QDialog(); + dialog->setCaption(i18n("Maximal Number of Clients")); + QHBoxLayout* l = new QHBoxLayout(dialog, KDialog::marginHint(), KDialog::spacingHint()); + l->setAutoAdd(true); + + (void) new QLabel(i18n("Maximal number of clients (-1 = infinite):"), dialog); + QLineEdit* edit = new QLineEdit(dialog);//TODO: use KIntNumInput +// edit->setText(QString::number(max)); // current max clients! //TODO + if (dialog->exec() == QDialog::Accepted) { + bool ok; + max = edit->text().toInt(&ok); + if (ok) { + game()->setMaxClients(max); + } + } + +} + +void KGameDialogMsgServerConfig::slotRemoveClient() +{ +} + +void KGameDialogMsgServerConfig::slotChangeAdmin() +{ + if (!game()) { + kdError(11001) << k_funcinfo << ": no valid game object available!" << endl; + return; + } + if (!admin()) { + kdError(11001) << k_funcinfo << ": only ADMIN is allowed to call this!" << endl; + return; + } + //TODO + Q_UINT32 newAdmin = 0; +// newAdmin = ; + game()->electAdmin(newAdmin); +} + +void KGameDialogMsgServerConfig::removeClient(Q_UINT32 /*id*/) +{ +//TODO +} + +void KGameDialogMsgServerConfig::setAdmin(bool a) +{ + if (admin() == a) { + // no need to do anything + return; + } + KGameDialogConfig::setAdmin(a); + if (admin()) { + if (d->noAdmin) { + delete d->noAdmin; + d->noAdmin = 0; + } + d->changeMaxClients = new QPushButton(i18n("Change Maximal Number of Clients"), this); + connect(d->changeMaxClients, SIGNAL(pressed()), this, SLOT(slotChangeMaxClients())); + d->changeAdmin = new QPushButton(i18n("Change Admin"), this); + connect(d->changeAdmin, SIGNAL(pressed()), this, SLOT(slotChangeAdmin())); + d->removeClient = new QPushButton(i18n("Remove Client with All Players"), this); + connect(d->removeClient, SIGNAL(pressed()), this, SLOT(slotRemoveClient())); + d->senderLayout->addWidget(d->changeMaxClients); + d->senderLayout->addWidget(d->changeAdmin); + d->senderLayout->addWidget(d->removeClient); + } else { + if (d->changeMaxClients) { + delete d->changeMaxClients; + d->changeMaxClients = 0; + } + if (d->changeAdmin) { + delete d->changeAdmin; + d->changeAdmin = 0; + } + if (d->removeClient) { + delete d->removeClient; + d->removeClient = 0; + } + d->noAdmin = new QLabel(i18n("Only the admin can configure the message server!"), this); + d->senderLayout->addWidget(d->noAdmin); + } +} + + +void KGameDialogMsgServerConfig::setHasMsgServer(bool has) +{ + if (!has) { + // delete all inputs + if (!d->noMaster) { + d->noMaster = new QLabel(i18n("You don't own the message server"), this); + d->localLayout->addWidget(d->noMaster); + } + return; + } + if (d->noMaster) { + delete d->noMaster; + d->noMaster = 0; + } + //TODO + // list all connections, data (max clients) and so on + // cannot be done above (together with QPushButtons) as it is possible that + // this client is ADMIN but not MASTER (i.e. doesn't own the messageserver) +} + + +class KGameDialogChatConfigPrivate +{ +public: + KGameDialogChatConfigPrivate() + { + mChat = 0; + } + + KGameChat* mChat; +}; + +KGameDialogChatConfig::KGameDialogChatConfig(int chatMsgId, QWidget* parent) + : KGameDialogConfig(parent) +{ + d = new KGameDialogChatConfigPrivate; + QVBoxLayout* topLayout = new QVBoxLayout(this, KDialog::marginHint(), KDialog::spacingHint()); + topLayout->setAutoAdd(true); + QHGroupBox* b = new QHGroupBox(i18n("Chat"), this); + d->mChat = new KGameChat(0, chatMsgId, b); +} + +KGameDialogChatConfig::~KGameDialogChatConfig() +{ + kdDebug(11001) << k_funcinfo << endl; + delete d; +} + +void KGameDialogChatConfig::setKGame(KGame* g) +{ + KGameDialogConfig::setKGame(g); + d->mChat->setKGame(game()); + if (!game()) { + hide(); + } else { + show(); + } +} + +void KGameDialogChatConfig::setOwner(KPlayer* p) +{ + KGameDialogConfig::setOwner(p); + if (!owner()) { + hide(); + return; + } + d->mChat->setFromPlayer(owner()); + show(); +} + + + +class KGameDialogConnectionConfigPrivate +{ +public: + KGameDialogConnectionConfigPrivate() + { + mPlayerBox = 0; + } + + QPtrDict<KPlayer> mItem2Player; + KListBox* mPlayerBox; +}; + +KGameDialogConnectionConfig::KGameDialogConnectionConfig(QWidget* parent) + : KGameDialogConfig(parent) +{ + //TODO: prevent player to ban himself + d = new KGameDialogConnectionConfigPrivate; + QVBoxLayout* topLayout = new QVBoxLayout(this, KDialog::marginHint(), KDialog::spacingHint()); + topLayout->setAutoAdd(true); + QHGroupBox* b = new QHGroupBox(i18n("Connected Players"), this); + d->mPlayerBox = new KListBox(b); + setMinimumHeight(100); +} + +KGameDialogConnectionConfig::~KGameDialogConnectionConfig() +{ + kdDebug(11001) << k_funcinfo << endl; + // d->mIem2Player.clear(); + delete d; +} + +void KGameDialogConnectionConfig::setKGame(KGame* g) +{ + if (game()) { + disconnect(game(), 0, this, 0); + } + KGameDialogConfig::setKGame(g); + slotClearPlayers(); + if (game()) { +// react to changes in KGame::playerList() + connect(game(), SIGNAL(signalPlayerJoinedGame(KPlayer*)), + this, SLOT(slotPlayerJoinedGame(KPlayer*))); + connect(game(), SIGNAL(signalPlayerLeftGame(KPlayer*)), + this, SLOT(slotPlayerLeftGame(KPlayer*))); + + KGame::KGamePlayerList l = *game()->playerList(); + for (KPlayer* p = l.first(); p; p = l.next()) { + slotPlayerJoinedGame(p); + } + } +} + +void KGameDialogConnectionConfig::setOwner(KPlayer* p) +{ + KGameDialogConfig::setOwner(p); +} + +void KGameDialogConnectionConfig::setAdmin(bool a) +{ + if (!game()) {// not possible... in theory + return; + } + if (admin()) { + disconnect(game(), SIGNAL(executed(QListBoxItem*)), this, 0); + } + KGameDialogConfig::setAdmin(a); + if (admin()) { + connect(d->mPlayerBox, SIGNAL(executed(QListBoxItem*)), this, + SLOT(slotKickPlayerOut(QListBoxItem*))); + } +} + +QListBoxItem* KGameDialogConnectionConfig::item(KPlayer* p) const +{ + QPtrDictIterator<KPlayer> it(d->mItem2Player); + while (it.current()) { + if (it.current() == p) { + return (QListBoxItem*)it.currentKey(); + } + ++it; + } + return 0; +} + +void KGameDialogConnectionConfig::slotClearPlayers() +{ + QPtrDictIterator<KPlayer> it(d->mItem2Player); + while (it.current()) { + slotPlayerLeftGame(it.current()); + ++it; + } + + if (d->mItem2Player.count() > 0) { + kdWarning(11001) << k_funcinfo << ": itemList wasn't cleared properly" << endl; + d->mItem2Player.clear(); + } + if (d->mPlayerBox->count() > 0) { + kdWarning(11001) << k_funcinfo << ": listBox wasn't cleared properly" << endl; + d->mPlayerBox->clear(); + } + +} + +void KGameDialogConnectionConfig::slotPlayerJoinedGame(KPlayer* p) +{ + if (!p) { + kdError(11001) << k_funcinfo << ": Cannot add NULL player" << endl; + } + if (d->mItem2Player[p]) { + kdError(11001) << k_funcinfo << ": attempt to double add player" << endl; + return; + } + kdDebug(11001) << k_funcinfo << ": add player " << p->id() << endl; + QListBoxText* t = new QListBoxText(p->name()); + d->mItem2Player.insert(t, p); + d->mPlayerBox->insertItem(t); + + connect(p, SIGNAL(signalPropertyChanged(KGamePropertyBase*, KPlayer*)), + this, SLOT(slotPropertyChanged(KGamePropertyBase*, KPlayer*))); + +} + +void KGameDialogConnectionConfig::slotPlayerLeftGame(KPlayer* p) +{ + // disconnect first + this->disconnect(p); + if (!item(p)) { + kdError(11001) << k_funcinfo << ": cannot find " << p->id() + << " in list" << endl; + return; + } + d->mPlayerBox->removeItem(d->mPlayerBox->index(item(p))); + +} + +void KGameDialogConnectionConfig::slotKickPlayerOut(QListBoxItem* item) +{ + kdDebug(11001) << "kick player out" << endl; + KPlayer* p = d->mItem2Player[item]; + if (!p) { + kdError(11001) << "invalid item selected - no player found" << endl; + return; + } + if (!game()) { + kdWarning(11001) << "no game set" << endl; + return; + } + if (!admin()) { + kdDebug(11001) << "Only the ADMIN can kick players" << endl; + return; + } + if (p == owner()) { // you wanna ban the ADMIN ?? + kdDebug(11001) << "you cannot kick the ADMIN" << endl; + return; + } + + if (KMessageBox::questionYesNo(this, i18n("Do you want to ban player \"%1\" from the game?").arg( + p->name()), QString::null, i18n("Ban Player"), i18n("Do Not Ban")) == KMessageBox::Yes) { + kdDebug(11001) << "will remove player " << p << endl; + game()->removePlayer(p); +// d->mPlayerBox->removeItem(d->mPlayerBox->index(item)); // should be done by signalPlayerLeftGame + } else { + kdDebug(11001) << "will NOT remove player " << p << endl; + } +} + +void KGameDialogConnectionConfig::slotPropertyChanged(KGamePropertyBase* prop, KPlayer* player) +{ + if(prop->id() == KGamePropertyBase::IdName) { + QListBoxText* old = 0; + QPtrDictIterator<KPlayer> it(d->mItem2Player); + while (it.current() && !old) { + if (it.current() == player) { + old = (QListBoxText*)it.currentKey(); + } + ++it; + } + QListBoxText* t = new QListBoxText(player->name()); + d->mPlayerBox->changeItem(t, d->mPlayerBox->index(old)); + d->mItem2Player.remove(old); + d->mItem2Player.insert(t, player); + } +} + diff --git a/libkdegames/kgame/dialogs/kgamedialogconfig.h b/libkdegames/kgame/dialogs/kgamedialogconfig.h new file mode 100644 index 00000000..b7d30b23 --- /dev/null +++ b/libkdegames/kgame/dialogs/kgamedialogconfig.h @@ -0,0 +1,362 @@ +/* + This file is part of the KDE games library + Copyright (C) 2001 Andreas Beckermann ([email protected]) + Copyright (C) 2001 Martin Heni ([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. +*/ + +// NAMING +// please follow these naming rules if you add/change classes: +// the main dialog is named KGameDialog and the base config widget +// KGameDialogConfig. All config widgets are named KGameDialogXYZConfig (where +// XYZ = the name of the config widget, like "general" or "network") and are +// inherited from KGameDialogConfig. + +#ifndef __KGAMEDIALOGCONFIG_H__ +#define __KGAMEDIALOGCONFIG_H__ + +#include <qwidget.h> +#include <kdemacros.h> + +class QGridLayout; +class QVBoxLayout; +class QListBoxItem; + +class KGame; +class KPlayer; +class KGamePropertyBase; + +class KGameDialogConfigPrivate; +/** + * Base class for configuration widgets. + * + * You can inherit from this and implement @ref submitToKGame, @ref + * setOwner and @ref setKGame to create your personal @ref KGame configuration widget :-) + * @short Base class for configuration widgets + * @author Andreas Beckermann <[email protected]> + **/ +class KDE_EXPORT KGameDialogConfig : public QWidget +{ + Q_OBJECT +public: + KGameDialogConfig(QWidget* parent = 0); + virtual ~KGameDialogConfig(); + + /** + * Called by @ref KGameDialog to submit all settings to the KGame + * Object. + * You have to replace this if you add your own widgets! + * @param g A pointer to your KGame. + * @param p A pointer to the player owning this dialog + **/ + virtual void submitToKGame(KGame* g, KPlayer* p) = 0; + + /** + * The owner player of the dialog has been changed. The default + * changes the pointer for owner so don't forget to call the + * default implementation if you overwrite this! + * + * You can use this e.g. to change a line edit widget containing the + * player name. + * + * Note: even NULL players are allowed! + * @param p The new owner player of the dialog + **/ + virtual void setOwner(KPlayer* p); + + /** + * The KGame object of the dialog has been changed. The default + * implementation changes the pointer for game so don't forget to + * call the default implementation if you overwrite this! + * + * You can use this e.g. to re-read the min/max player settings. + * @param g The KGame object + **/ + virtual void setKGame(KGame* g); + + /** + * The admin status has been changed. + * If the KGame object of this config widget is the + * admin the user is allowed to configure it. Otherwise most + * widgets will have to be disabled. Note that you don't necessarily + * need to deactivate all widget - e.g. the player name must be + * configured by the player. Mainly the KGame configuration can be done + * by the admin only. + * + * By default this does nothing. Changes the value for admin so + * don't forget to call the default implementation in derived classes! + * @param admin Whether the KGame object of this dialog can be + * configured + **/ + virtual void setAdmin(bool admin); + + /** + * A pointer to the KGame object that has been set by @ref setKGame. + * + * Note that NULL is allowed! + * @return The KGame object assigned to this dialog + **/ + KGame* game() const; + + /** + * A pointer to the KPlayer object that has been set by @ref + * setOwner. + * + * Note that NULL is allowed! + * @return The owner of the dialog + **/ + KPlayer* owner() const; + + /** + * @return True if the owner is ADMIN otherwise FALSE. See also + * @ref setAdmin + **/ + bool admin() const; + +protected: + +private: + KGameDialogConfigPrivate* d; +}; + +/** + * The main game configuration widget. + * + * It currently contains a line edit for the name of the player only. You can + * add widgets by using the KGameDialogGeneralConfig as parent parameter as it + * uses QLayout::autoAdd == true. + * @author Andreas Beckermann <[email protected]> + **/ +class KGameDialogGeneralConfigPrivate; +class KGameDialogGeneralConfig : public KGameDialogConfig +{ + Q_OBJECT +public: + /** + * Construct a KGameDialogGeneralConfig. Currently it contains a line + * edit widget to change the player name only. + * + * If you just want to add more widgets you can just create your widgets + * with the KGameDialogGeneralConfig as parent as it uses + * QLayout::setAutoAdd(true). + * + * @param parent Parent widget for this dialog. + * @param initializeGUI If you really don't want to use the + * predefined widget and/or layout use FALSE here. Note that then none + * of the predefined widgets (currently only the name of the player) + * will exist anymore. + * + **/ + KGameDialogGeneralConfig(QWidget* parent = 0, bool initializeGUI = true); + virtual ~KGameDialogGeneralConfig(); + + /** + * Called by @ref KGameDialog to submit all settings to the KGame + * Object. + * You have to replace this if you add your own widgets! + * @param g A pointer to your KGame. + * @param p A pointer to the player owning this dialog + **/ + virtual void submitToKGame(KGame* g, KPlayer* p); + + /** + * Change the owner of the config widget. + * + * Changes the playername in the line edit + * @param p The new owner player + **/ + virtual void setOwner(KPlayer* p); + + /** + * See @ref KGameDialogConfig::setKGame + * + * Sets the default values of all KGame related predefined widgets + * (currently none) + **/ + virtual void setKGame(KGame* g); + + /** + * See @ref KGameDialogConfig::setAdmin + * + * This deactivates the min/max player widgets + **/ + virtual void setAdmin(bool admin); + +protected slots: + void slotPropertyChanged(KGamePropertyBase*, KPlayer*); + +protected: + void setPlayerName(const QString& name); + + QString playerName() const; + +private: + KGameDialogGeneralConfigPrivate* d; +}; + +class KGameDialogNetworkConfigPrivate; +class KDE_EXPORT KGameDialogNetworkConfig : public KGameDialogConfig +{ + Q_OBJECT +public: + KGameDialogNetworkConfig(QWidget* parent = 0); + virtual ~KGameDialogNetworkConfig(); + + + void disableInitConnection(); + + /** + * Called by @ref KGameDialog to submit all settings to the KGame + * Object. + * You have to replace this if you add your own widgets! + * @param g A pointer to your KGame. + * @param p A pointer to the player owning this dialog + **/ + virtual void submitToKGame(KGame* g, KPlayer* p); + + virtual void setKGame(KGame* g); + + /** + * This sets the default port and host used in @ref KGameConnectDialog. + * The user will be able to change these defaults! + * + * If you don't call this then host "localhost" and port "0" is used. + * You are strongly encouraged to change at least the port! + * @param port The default port to connect to / listen on + * @param host The default host to connect to + **/ + void setDefaultNetworkInfo(const QString& host, unsigned short int port,bool server=true); + + /** + * Set service type that will be published or browsed for and game name that will be displayed in + * server browser. Without this publishing and discovery of LAN servers will not be enabled. + * @param name Game name. Important only for server mode. If not + * set hostname will be used. In case of name conflict -2, -3 and so on will be added to name. + * @param type Service type (something like _kwin4._tcp). It should be unique for application. + * @since 3.4 + **/ + void setDiscoveryInfo(const QString& type, const QString& name=QString::null); + +signals: + /** + * This signal is emmited if the user changes the server type (client/server) + * in the network configuration dialog. + * + * @param t - type type (0/1) of the connection + **/ + void signalServerTypeChanged(int); + + +protected: + void setConnected(bool connected, bool master = false); + +protected slots: + void slotInitConnection(); + void slotExitConnection(); + void slotConnectionBroken(); + + +private: + KGameDialogNetworkConfigPrivate* d; +}; + +class KGameDialogMsgServerConfigPrivate; +class KGameDialogMsgServerConfig : public KGameDialogConfig +{ + Q_OBJECT +public: + KGameDialogMsgServerConfig(QWidget* parent = 0); + virtual ~KGameDialogMsgServerConfig(); + + virtual void submitToKGame(KGame*, KPlayer*) {} + + void setHasMsgServer(bool); + + virtual void setKGame(KGame* g); + virtual void setAdmin(bool); + +protected slots: + void slotChangeMaxClients(); + void slotChangeAdmin(); + void slotRemoveClient(); + +protected: + void removeClient(Q_UINT32 id); + +private: + KGameDialogMsgServerConfigPrivate* d; +}; + +class KGameDialogChatConfigPrivate; +/** + * This is not really a configuration widget but rather a simple chat widget. + * This widget does nothing but just providing a @ref KGameChat object. + * @short A chat widget inside a @ref KGameDialog + * @author Andreas Beckermann <[email protected]> + **/ +class KGameDialogChatConfig : public KGameDialogConfig +{ + Q_OBJECT +public: + KGameDialogChatConfig(int chatMsgId, QWidget* parent = 0); + virtual ~KGameDialogChatConfig(); + + virtual void setKGame(KGame* g); + virtual void setOwner(KPlayer* p); + + virtual void submitToKGame(KGame* g, KPlayer* p) { Q_UNUSED(g); Q_UNUSED(p); } + +private: + KGameDialogChatConfigPrivate* d; +}; + +/** + * @short Lists all connected players and gives the ability to kick them off the + * game + **/ +class KGameDialogConnectionConfigPrivate; +class KGameDialogConnectionConfig : public KGameDialogConfig +{ + Q_OBJECT +public: + KGameDialogConnectionConfig(QWidget* parent = 0); + virtual ~KGameDialogConnectionConfig(); + + virtual void setKGame(KGame* g); + virtual void setOwner(KPlayer* p); + virtual void setAdmin(bool admin); + + virtual void submitToKGame(KGame* g, KPlayer* p) { Q_UNUSED(g); Q_UNUSED(p); } + +protected: + /** + * @param p A player + * @return The QListBoxItem that belongs to the player @p p + **/ + QListBoxItem* item(KPlayer* p) const; + +protected slots: + void slotKickPlayerOut(QListBoxItem* item); + void slotPropertyChanged(KGamePropertyBase* prop, KPlayer* p); + void slotPlayerLeftGame(KPlayer* p); + void slotPlayerJoinedGame(KPlayer* p); + void slotClearPlayers(); + +private: + KGameDialogConnectionConfigPrivate* d; + +}; +#endif diff --git a/libkdegames/kgame/dialogs/kgameerrordialog.cpp b/libkdegames/kgame/dialogs/kgameerrordialog.cpp new file mode 100644 index 00000000..1750892c --- /dev/null +++ b/libkdegames/kgame/dialogs/kgameerrordialog.cpp @@ -0,0 +1,129 @@ +/* + This file is part of the KDE games library + Copyright (C) 2001 Andreas Beckermann ([email protected]) + Copyright (C) 2001 Martin Heni ([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. +*/ + +#include <kmessagebox.h> +#include <klocale.h> +#include <kdebug.h> + +#include "kgame.h" + +#include "kgameerrordialog.h" + +class KGameErrorDialogPrivate +{ +public: + KGameErrorDialogPrivate() + { + mGame = 0; + } + + const KGame* mGame; +}; + +KGameErrorDialog::KGameErrorDialog(QWidget* parent) : QObject(parent) +{ + d = new KGameErrorDialogPrivate; +} + +KGameErrorDialog::~KGameErrorDialog() +{ + delete d; +} + +void KGameErrorDialog::setKGame(const KGame* g) +{ + slotUnsetKGame(); + d->mGame = g; + + connect(d->mGame, SIGNAL(destroyed()), this, SLOT(slotUnsetKGame())); + +// the error signals: + connect(d->mGame, SIGNAL(signalNetworkErrorMessage(int, QString)), + this, SLOT(slotError(int, QString))); + connect(d->mGame, SIGNAL(signalConnectionBroken()), + this, SLOT(slotServerConnectionLost())); + connect(d->mGame, SIGNAL(signalClientDisconnected(Q_UINT32,bool)), + this, SLOT(slotClientConnectionLost(Q_UINT32,bool))); +} + +void KGameErrorDialog::slotUnsetKGame() +{ + if (d->mGame) { + disconnect(d->mGame, 0, this, 0); + } + d->mGame = 0; +} + +void KGameErrorDialog::error(const QString& errorText, QWidget* parent) +{ KMessageBox::error(parent, errorText); } + +void KGameErrorDialog::slotServerConnectionLost() +{ +// TODO: add IP/port of the server + QString message = i18n("Connection to the server has been lost!"); + error(message, (QWidget*)parent()); +} + +void KGameErrorDialog::slotClientConnectionLost(Q_UINT32 /*id*/,bool) +{ +//TODO: add IP/port of the client + QString message; +// if (c) { +// message = i18n("Connection to client has been lost!\nID: %1\nIP: %2").arg(c->id()).arg(c->IP()); +// } else { +// message = i18n("Connection to client has been lost!"); +// } + message = i18n("Connection to client has been lost!"); + error(message, (QWidget*)parent()); +} + +void KGameErrorDialog::slotError(int errorNo, QString text) +{ + QString message = i18n("Received a network error!\nError number: %1\nError message: %2").arg(errorNo).arg(text); + error(message, (QWidget*)parent()); +} + +void KGameErrorDialog::connectionError(QString s) +{ + QString message; + if (s.isNull()) { + message = i18n("No connection could be created."); + } else { + message = i18n("No connection could be created.\nThe error message was:\n%1").arg(s); + } + error(message, (QWidget*)parent()); +} + + + +// should become the real dialog - currently we just use messageboxes +// -> maybe unused forever +KGameErrorMessageDialog::KGameErrorMessageDialog(QWidget* parent) + : KDialogBase(Plain, i18n("Error"), Ok, Ok, parent, 0, true, true) +{ +} + +KGameErrorMessageDialog::~KGameErrorMessageDialog() +{ +} + + + +#include "kgameerrordialog.moc" diff --git a/libkdegames/kgame/dialogs/kgameerrordialog.h b/libkdegames/kgame/dialogs/kgameerrordialog.h new file mode 100644 index 00000000..c1dbd1ca --- /dev/null +++ b/libkdegames/kgame/dialogs/kgameerrordialog.h @@ -0,0 +1,113 @@ +/* + This file is part of the KDE games library + Copyright (C) 2001 Andreas Beckermann ([email protected]) + Copyright (C) 2001 Martin Heni ([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. +*/ + +#ifndef __KGAMEERRORDIALOG_H__ +#define __KGAMEERRORDIALOG_H__ + +#include <kdialogbase.h> + +class KGame; +class KGameErrorDialogPrivate; + +/** + * Use error(), warning() and information() to display the information about a + * network game. Maybe a better solution is to use KMessageBoxes + * You can connect to the public slots, too - they will call the static + * functions, so that you can always have a KGameErrorDialog object lying around + * without losing much memory (a KGameErrorMessageDialog Object will be + * created) + * @short Error handling for KGame + * @author Andreas Beckermann <[email protected]> + **/ +class KGameErrorDialog : public QObject +{ + Q_OBJECT +public: + KGameErrorDialog(QWidget* parent); + ~KGameErrorDialog(); + + /** + * Automatically connects the KGame object to all error dependant slots. + * Create a KGameErrorDialog object, call this function and forget + * everything. + * @param g The KGame which will emit the erorrs (or not ;-) ) + **/ + void setKGame(const KGame* g); + + /** + * KGame couldn't establish a connection. Use this if + * KGame::initConnection returns false + * @param s A string that describes the error further (like port is + * already in use). Will be ignored if QString::null + **/ + void connectionError(QString s = QString::null); + +public slots: + void slotError(int error, QString text); + + /** + * The connection to the @ref KMessageServer has been lost + * + * See @ref KGameNetwork::signalConnectionBroken + **/ + void slotServerConnectionLost(); + + /** + * The connection to a client has been lost by accident + * + * See @ref KGameNetwork::signalClientDisconnected + **/ + void slotClientConnectionLost(Q_UINT32 clientID,bool broken); + + /** + * Unsets a @ref KGame which has been set using @ref setKGame before. + * This is called automatically when the @ref KGame object is destroyed + * and you normally don't have to call this yourself. + * + * Note that @ref setKGame also unsets an already existing @ref KGame + * object if exising. + **/ + void slotUnsetKGame(); + +protected: + void error(const QString& errorText, QWidget* parent = 0); + +private: + KGameErrorDialogPrivate* d; +}; + +/** + * The real class for error messages. KGameErrorDialog uses this to create error + * messages (not yet). + * Use @ref KGameErrorDialog instead. + * @short Internally used by @ref KGameErrorDialog + * @author Andreas Beckermann <[email protected]> + **/ +class KGameErrorMessageDialog : public KDialogBase +{ + Q_OBJECT +public: + KGameErrorMessageDialog(QWidget* parent); + ~KGameErrorMessageDialog(); + +private: +}; + +#endif 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 + */ diff --git a/libkdegames/kgame/kgame.h b/libkdegames/kgame/kgame.h new file mode 100644 index 00000000..37d8d974 --- /dev/null +++ b/libkdegames/kgame/kgame.h @@ -0,0 +1,932 @@ +/* + 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$ +*/ +#ifndef __KGAME_H_ +#define __KGAME_H_ + +#include <qstring.h> +#include <qptrlist.h> +#include <qvaluelist.h> + +#include "kgamenetwork.h" +#include <kdemacros.h> +class KRandomSequence; + +class KPlayer; +class KGamePropertyBase; +class KGamePropertyHandler; +class KGameSequence; + +class KGamePrivate; + +/** + * @short The main KDE game object + * + * The KGame class is the central game object. A game basically + * consists of following features: + * - Player handling (add, remove,...) + * - Game status (end,start,pause,...) + * - load/save + * - Move (and message) handling + * - nextPlayer and gameOver() + * - Network connection (for KGameNetwork) + * + * Example: + * \code + * KGame *game=new KGame; + * \endcode + * + * + * @author Martin Heni <[email protected]> + * + */ +class KDE_EXPORT KGame : public KGameNetwork +{ + Q_OBJECT + +public: + typedef QPtrList<KPlayer> KGamePlayerList; + + /** + * The policy of the property. This can be PolicyClean (setVale uses + * send), PolicyDirty (setValue uses changeValue) or + * PolicyLocal (setValue uses setLocal). + * + * A "clean" policy means that the property is always the same on every + * client. This is achieved by calling send which actually changes + * the value only when the message from the MessageServer is received. + * + * A "dirty" policy means that as soon as setValue is called the + * property is changed immediately. And additionally sent over network. + * This can sometimes lead to bugs as the other clients do not + * immediately have the same value. For more information see + * changeValue. + * + * PolicyLocal means that a KGameProperty behaves like ever + * "normal" variable. Whenever setValue is called (e.g. using "=") + * the value of the property is changes immediately without sending it + * over network. You might want to use this if you are sure that all + * clients set the property at the same time. + **/ + enum GamePolicy + { + PolicyUndefined = 0, + PolicyClean = 1, + PolicyDirty = 2, + PolicyLocal = 3 + }; + + /** + * Create a KGame object. The cookie is used to identify your + * game in load/save and network operations. Change this between + * games. + */ + KGame(int cookie=42,QObject* parent=0); + + /** + * Destructs the game + */ + virtual ~KGame(); + + /** + * Gives debug output of the game status + */ + virtual void Debug(); + + /** + * Game status - Use this to Control the game flow. + * The KGame e.g. sets the status to Pause when you have + * less player than the minimum amount + */ + enum GameStatus + { + Init = 0, + Run = 1, + Pause = 2, + End = 3, + Abort = 4, + SystemPause = 5, + Intro = 6, + UserStatus = 7 + }; + + // Properties + /** + * Returns a list of all active players + * + * @return the list of players + */ + KGamePlayerList *playerList(); + + /** + * The same as @ref playerList but returns a const pointer. + **/ + const KGamePlayerList *playerList() const; + + /** + * Returns a list of all inactive players + * @return the list of players + */ + KGamePlayerList *inactivePlayerList(); + + /** + * The same as @ref inactivePlayerList but returns a const pointer. + **/ + const KGamePlayerList *inactivePlayerList() const; + + /** + * Returns a pointer to the game's KRandomSequence. This sequence is + * identical for all network players! + * @return KRandomSequence pointer + */ + KRandomSequence *random() const; + + /** + * @return The KGameSequence object that is currently in use. + * @see setGameSequence + **/ + KGameSequence *gameSequence() const; + + /** + * Is the game running + * @return true/false + */ + bool isRunning() const; + + // Player handling + /** + * Returns the player object for a given player id + * @param id Player id + * @return player object + */ + KPlayer *findPlayer(Q_UINT32 id) const; + + /** + * Set a new @ref KGameSequence to control player management. By default + * KGame uses a normal @ref KGameSequence object. You might want to subclass + * that and provide your own object. + * + * The previous sequence will get deleted. + * @param sequence The new game sequence object. KGame takes ownership and + * will delete it on destruction! + **/ + void setGameSequence(KGameSequence* sequence); + + /** + * Note that KPlayer::save must be implemented properly, as well as + * KPlayer::rtti + * This will only send a message to all clients. The player is _not_ added + * directly! + * See also playerInput which will be called as soon as the + * player really has been added. + * + * Note that an added player will first get into a "queue" and won't be in + * the game. It will be added to the game as soon as systemAddPlayer is + * called what will happen as soon as IdAddPlayer is received. + * + * Note: you probably want to connect to signalPlayerJoinedGame for + * further initialization! + * @param newplayer The player you want to add. KGame will send a message to + * all clients and add the player using systemAddPlayer + **/ + void addPlayer(KPlayer* newplayer); + + /** + * Sends a message over the network, msgid=IdRemovePlayer. + * + * As soon as this message is received by networkTransmission + * systemRemovePlayer is called and the player is removed. + **/ + //AB: TODO: make sendMessage to return if the message will be able to be + //sent, eg if a socket is connected, etc. If sendMessage returns false + //remove the player directly using systemRemovePlayer + bool removePlayer(KPlayer * player) { return removePlayer(player, 0); } + + /** + * Called by the destructor of KPlayer to remove itself from the game + * + **/ + void playerDeleted(KPlayer * player); + + /** + * sends activate player: internal use only? + */ + bool activatePlayer(KPlayer *player); + + /** + * sends inactivate player: internal use only? + */ + bool inactivatePlayer(KPlayer *player); + + /** + * Set the maximal number of players. After this is + * reached no more players can be added. You must be ADMIN to call this (@see + * isAdmin). + * @param maxnumber maximal number of players + */ + void setMaxPlayers(uint maxnumber); + + /** + * What is the maximal number of players? + * @return maximal number of players + */ + int maxPlayers() const; + + /** + * Set the minimal number of players. A game can not be started + * with less player resp. is paused when already running. You must be ADMIN + * to call this (see @ref isAdmin)! + * @param minnumber minimal number of players + */ + void setMinPlayers(uint minnumber); + + /** + * What is the minimal number of players? + * @return minimal number of players + */ + uint minPlayers() const; + + /** + * Returns how many players are plugged into the game + * @return number of players + */ + uint playerCount() const; + + /** + * @deprecated + * Use @ref KGameSequence::nextPlayer instead + **/ + virtual KPlayer * nextPlayer(KPlayer *last,bool exclusive=true); + + // Input events + /** + * Called by KPlayer to send a player input to the + * KMessageServer. + **/ + virtual bool sendPlayerInput(QDataStream &msg,KPlayer *player,Q_UINT32 sender=0); + + /** + * Called when a player input arrives from KMessageServer. + * + * Calls prepareNext (using QTimer::singleShot) if gameOver() + * returns 0. This function should normally not be used outside KGame. + * It could be made non-virtual,protected in a later version. At the + * moment it is a virtual function to give you more control over KGame. + * + * For documentation see playerInput. + **/ + virtual bool systemPlayerInput(QDataStream &msg,KPlayer *player,Q_UINT32 sender=0); + + /** + * This virtual function is called if the KGame needs to create a new player. + * This happens only over a network and with load/save. Doing nothing + * will create a default KPlayer. If you want to have your own player + * you have to create one with the given rtti here. + * Note: If your game uses a player class derived from KPlayer you MUST + * override this function and create your player here. Otherwise the + * game will crash. + * Example: + * \code + * KPlayer *MyGame::createPlayer(int rtti,int io,bool isvirtual) + * { + * KPlayer *player=new MyPlayer; + * if (!isvirtual) // network player ? + * { + * // Define something like this to add the IO modules + * createIO(player,(KGameIO::IOMode)io); + * } + * return player; + * } + * \endcode + * + * @param rtti is the type of the player (0 means default KPlayer) + * @param io is the 'or'ed rtti of the KGameIO's + * @param isvirtual true if player is virtual + */ + virtual KPlayer *createPlayer(int rtti,int io,bool isvirtual); + + // load/save + /** + * Load a saved game, from file OR network. This function has + * to be overwritten or you need to connect to the load signal + * if you have game data other than KGameProperty. + * For file load you should reset() the game before any load attempt + * to make sure you load into an clear state. + * + * @param stream a data stream where you can stream the game from + * @param reset - shall the game be reset before loading + * + * @return true? + */ + virtual bool load(QDataStream &stream,bool reset=true); + + /** + * Same as above function but with different parameters + * + * @param filename - the filename of the file to be opened + * @param reset - shall the game be reset before loading + * + * @return true? + **/ + virtual bool load(QString filename,bool reset=true); + + /** + * Save a game to a file OR to network. Otherwise the same as + * the load function + * + * @param stream a data stream to load the game from + * @param saveplayers If true then all players wil be saved too + * + * @return true? + */ + virtual bool save(QDataStream &stream,bool saveplayers=true); + + /** + * Same as above function but with different parameters + * + * @param filename the filename of the file to be saved + * @param saveplayers If true then all players wil be saved too + * + * @return true? + **/ + virtual bool save(QString filename,bool saveplayers=true); + + /** + * Resets the game, i.e. puts it into a state where everything + * can be started from, e.g. a load game + * Right now it does only need to delete all players + * + * @return true on success + */ + virtual bool reset(); + + + // Game sequence + /** + * returns the game status, ie running,pause,ended,... + * + * @return game status + */ + int gameStatus() const; + + /** + * sets the game status + * + * @param status the new status + */ + void setGameStatus(int status); + + /** + * docu: see KPlayer + **/ + bool addProperty(KGamePropertyBase* data); + + /** + * This is called by KPlayer::sendProperty only! Internal function! + **/ + bool sendPlayerProperty(int msgid, QDataStream& s, Q_UINT32 playerId); + + /** + * This function allows to find the pointer to a player + * property when you know it's id + */ + KGamePropertyBase* findProperty(int id) const; + + /** + * Changes the consistency policy of a property. The + * GamePolicy is one of PolicyClean (default), PolicyDirty or PolicyLocal. + * + * It is up to you to decide how you want to work. + **/ + void setPolicy(GamePolicy p,bool recursive=true); + + /** + * @return The default policy of the property + **/ + GamePolicy policy() const; + + /** + * See KGameNetwork::sendMessage + * + * Send a network message msg with a given message ID msgid to all players of + * a given group (see KPlayer::group) + * @param msg the message which will be send. See messages.txt for contents + * @param msgid an id for this message + * @param sender the id of the sender + * @param group the group of the receivers + * @return true if worked + */ + bool sendGroupMessage(const QByteArray& msg, int msgid, Q_UINT32 sender, const QString& group); + bool sendGroupMessage(const QDataStream &msg, int msgid, Q_UINT32 sender, const QString& group); + bool sendGroupMessage(int msg, int msgid, Q_UINT32 sender, const QString& group); + bool sendGroupMessage(const QString& msg, int msgid, Q_UINT32 sender, const QString& group); + + /** + * This will either forward an incoming message to a specified player + * (see KPlayer::networkTransmission) or + * handle the message directly (e.g. if msgif==IdRemovePlayer it will remove + * the (in the stream) specified player). If both is not possible (i.e. the + * message is user specified data) the signal signalNetworkData is + * emitted. + * + * This emits signalMessageUpdate <em>before</em> doing anything with + * the message. You can use this signal when you want to be notified about + * an update/change. + * @param msgid Specifies the kind of the message. See messages.txt for + * further information + * @param stream The message that is being sent + * @param receiver The is of the player this message is for. 0 For broadcast. + * @param sender + * @param clientID the client from which we received the transmission - hardly used + **/ + virtual void networkTransmission(QDataStream &stream, int msgid, Q_UINT32 receiver, Q_UINT32 sender, Q_UINT32 clientID); + + /** + * Returns a pointer to the KGame property handler + **/ + KGamePropertyHandler* dataHandler() const; + +protected slots: + /** + * Called by KGamePropertyHandler only! Internal function! + **/ + void sendProperty(int msgid, QDataStream& stream, bool* sent); + + /** + * Called by KGamePropertyHandler only! Internal function! + **/ + void emitSignal(KGamePropertyBase *me); + + /** + * @deprecated + * Use KGameSequence::prepareNext() instead + **/ + virtual void prepareNext(); + + + /** + * Calls negotiateNetworkGame() + * See KGameNetwork::signalClientConnected + **/ + void slotClientConnected(Q_UINT32 clientId); + + /** + * This slot is called whenever the connection to a client is lost (ie the + * signal KGameNetwork::signalClientDisconnected is emitted) and will remove + * the players from that client. + * @param clientId The client the connection has been lost to + * @param broken (ignore this - not used) + **/ + void slotClientDisconnected(Q_UINT32 clientId,bool broken); + + /** + * This slot is called whenever the connection to the server is lost (ie the + * signal KGameNetwork::signalConnectionBroken is emitted) and will + * switch to local game mode + **/ + void slotServerDisconnected(); + +signals: + /** + * When a client disconnects from the game usually all players from that + * client are removed. But if you use completely the KGame structure you + * probably don't want this. You just want to replace the KGameIO of the + * (human) player by a computer KGameIO. So this player continues game but + * is from this point on controlled by the computer. + * + * You achieve this by connecting to this signal. It is emitted as soon as a + * client disconnects on <em>all</em> other clients. Make sure to add a new + * KGameIO only once! you might want to use @ref isAdmin for this. If you + * added a new KGameIO set *remove=false otherwise the player is completely + * removed. + * @param player The player that is about to be removed. Add your new + * KGameIO here - but only on <em>one</em> client! + * @param remove Set this to FALSE if you don't want this player to be + * removed completely. + **/ + void signalReplacePlayerIO(KPlayer* player, bool* remove); + + /** + * The game will be loaded from the given stream. Load from here + * the data which is NOT a game or player property. + * It is not necessary to use this signal for a full property game. + * + * This signal is emitted <em>before</em> the players are loaded by + * KGame. See also signalLoad + * + * You must load <em>exactly</em> the same data from the stream that you have saved + * in signalSavePrePlayers. Otherwise player loading will not work + * anymore. + * + * @param stream the load stream + */ + void signalLoadPrePlayers(QDataStream &stream); + + /** + * The game will be loaded from the given stream. Load from here + * the data which is NOT a game or player property. + * It is not necessary to use this signal for a full property game. + * + * @param stream the load stream + */ + void signalLoad(QDataStream &stream); + + /** + * The game will be saved to the given stream. Fill this with data + * which is NOT a game or player property. + * It is not necessary to use this signal for a full property game. + * + * This signal is emitted <em>before</em> the players are saved by + * KGame. See also signalSave + * + * If you can choose between signalSavePrePlayers and signalSave then + * better use signalSave + * + * @param stream the save stream + **/ + void signalSavePrePlayers(QDataStream &stream); + + /** + * The game will be saved to the given stream. Fill this with data + * which is NOT a game or player property. + * It is not necessary to use this signal for a full property game. + * + * @param stream the save stream + */ + void signalSave(QDataStream &stream); + + /** + * Is emmited if a game with a different version cookie is loaded. + * Normally this should result in an error. But maybe you do support + * loading of older game versions. Here would be a good place to do a + * conversion. + * + * @param stream - the load stream + * @param network - true if this is a network connect. False for load game + * @param cookie - the saved cookie. It differs from KGame::cookie() + * @param result - set this to true if you managed to load the game + */ + void signalLoadError(QDataStream &stream,bool network,int cookie, bool &result); + + /** + * We got an user defined update message. This is usually done + * by a sendData in a inherited KGame Object which defines its + * own methods and has to syncronise them over the network. + * Reaction to this is usually a call to a KGame function. + */ + void signalNetworkData(int msgid,const QByteArray& buffer, Q_UINT32 receiver, Q_UINT32 sender); + + /** + * We got an network message. this can be used to notify us that something + * changed. What changed can be seen in the message id. Whether this is + * the best possible method to do this is unclear... + */ + void signalMessageUpdate(int msgid,Q_UINT32 receiver,Q_UINT32 sender); + + /** + * a player left the game because of a broken connection or so! + * + * Note that when this signal is emitted the player is not part of @ref + * playerList anymore but the pointer is still valid. You should do some + * final cleanups here since the player is usually deleted after the signal + * is emitted. + * + * @param player the player who left the game + */ + void signalPlayerLeftGame(KPlayer *player); + + /** + * a player joined the game + * + * @param player the player who joined the game + */ + void signalPlayerJoinedGame(KPlayer *player); + + + /** + * This signal is emmited if a player property changes its value and + * the property is set to notify this change + */ + void signalPropertyChanged(KGamePropertyBase *property, KGame *me); + + /** + * Is emitted after a call to gameOver() returns a non zero + * return code. This code is forwarded to this signal as 'status'. + * + * @param status the return code of gameOver() + * @param current the player who did the last move + * @param me a pointer to the KGame object + */ + void signalGameOver(int status, KPlayer *current, KGame *me); + + /** + * Is emmited after a client is successfully connected to the game. + * The client id is the id of the new game client. An easy way to + * check whether that's us is + * \code + * if (clientid==gameid()) .. // we joined + * else ... // someone joined the game + * \endcode + * @param clientid - The id of the new client + * @param me - our game pointer + */ + void signalClientJoinedGame(Q_UINT32 clientid,KGame *me); + + /** + * This signal is emitted after a network partner left the + * game (either by a broken connection or voluntarily). + * All changes to the network players have already be done. + * If there are not enough players left, the game might have + * been paused. To check this you get the old gamestatus + * before the disconnection as argument here. The id of the + * client who left the game allows to distinguish who left the + * game. If it is 0, the server disconnected and you were a client + * which has been switched back to local play. + * You can use this signal to, e.g. set some menues back to local + * player when they were network before. + * + * @param clientID - 0:server left, otherwise the client who left + * @param oldgamestatus - the gamestatus before the loss + * @param me - our game pointer + **/ + void signalClientLeftGame(int clientID,int oldgamestatus,KGame *me); + + +protected: + /** + * A player input occurred. This is the most important function + * as the given message will contain the current move made by + * the given player. + * Note that you HAVE to overwrite this function. Otherwise your + * game makes no sense at all. + * Generally you have to return TRUE in this function. Only then + * the game sequence is proceeded by calling @ref playerInputFinished + * which in turn will check for game over or the next player + * However, if you have a delayed move, because you e.g. move a + * card or a piece you want to return FALSE to pause the game sequence + * and then manually call @ref playerInputFinished to resume it. + * Example: + * \code + * bool MyClass::playerInput(QDataStream &msg,KPlayer *player) + * { + * Q_INT32 move; + * msg >> move; + * kdDebug() << " Player " << player->id() << " moved to " << move << + * endl; + * return true; + * } + * \endcode + * + * @param msg the move message + * @param player the player who did the move + * @return true - input ready, false: input manual + */ + virtual bool playerInput(QDataStream &msg,KPlayer *player)=0; + + + /** + * Called after the player input is processed by the game. Here the + * checks for game over and nextPlayer (in the case of turn base games) + * are processed. + * Call this manually if you have a delayed move, i.e. your playerInput + * function returns FALSE. If it returns true you need not do anything + * here. + * + * @return the current player + * + **/ + KPlayer *playerInputFinished(KPlayer *player); + + + /** + * This virtual function can be overwritten for your own player management. + * It is called when a new game connects to an existing network game or + * to the network master. In case you do not want all players of both games + * to be present in the new network game, you can deactivate players here. + * This is of particular importance if you have a game with fixed number of + * player like e.g. chess. A network connect needs to disable one player of + * each game to make sense. + * + * Not overwriting this function will activate a default behaviour which + * will deactivate players until the @ref maxPlayers() numebr is reached + * according to the KPlayer::networkPriority() value. Players with a low + * value will be kicked out first. With equal priority players of the new + * client will leave first. This means, not setting this value and not + * overwriting this function will never allow a chess game to add client + * players!!! + * On the other hand setting one player of each game to a networkPriorty of + * say 10, already does most of the work for you. + * + * The parameters of this function are the playerlist of the network game, + * which is @ref playerList(). The second argument is the player list of + * the new client who wants to join and the third argument serves as return + * parameter. All <em>player ID's</em> which are written into this list + * will be <em>removed</em> from the created game. You do this by an + * \code + * inactivate.append(player->id()); + * \endcode + * + * @param oldplayer - the list of the network players + * @param newplayer - the list of the client players + * @param inactivate - the value list of ids to be deactivated + * + **/ + virtual void newPlayersJoin(KGamePlayerList *oldplayer, + KGamePlayerList *newplayer, + QValueList<int> &inactivate) { + Q_UNUSED( oldplayer ); + Q_UNUSED( newplayer ); + Q_UNUSED( inactivate ); + } + + /** + * Save the player list to a stream. Used for network game and load/save. + * Can be overwritten if you know what you are doing + * + * @param stream is the stream to save the player ot + * @param list the optional list is the player list to be saved, default is playerList() + * + **/ + void savePlayers(QDataStream &stream,KGamePlayerList *list=0); + + /** + * Prepare a player for being added. Put all data about a player into the + * stream so that it can be sent to the KGameCommunicationServer using + * addPlayer (e.g.) + * + * This function ensures that the code for adding a player is the same in + * addPlayer as well as in negotiateNetworkGame + * @param stream is the stream to add the player + * @param player The player to add + **/ + void savePlayer(QDataStream& stream,KPlayer* player); + + /** + * Load the player list from a stream. Used for network game and load/save. + * Can be overwritten if you know what you are doing + * + * @param stream is the stream to save the player to + * @param isvirtual will set the virtual flag true/false + * + **/ + KPlayer *loadPlayer(QDataStream& stream,bool isvirtual=false); + + + /** + * inactivates player. Use @ref inactivatePlayer instead! + */ + bool systemInactivatePlayer(KPlayer *player); + + /** + * activates player. Use @ref activatePlayer instead! + */ + bool systemActivatePlayer(KPlayer *player); + + /** + * Adds a player to the game + * + * Use @ref addPlayer to send @ref KGameMessage::IdAddPlayer. As soon as + * this Id is received this function is called, where the player (see @ref + * KPlayer::rtti) is added as well as its properties (see @ref KPlayer::save + * and @ref KPlayer::load) + * + * This method calls the overloaded @ref systemAddPlayer with the created + * player as argument. That method will really add the player. + * If you need to do some changes to your newly added player just connect to + * @ref signalPlayerJoinedGame + */ + + /** + * Finally adds a player to the game and therefore to the list. + **/ + void systemAddPlayer(KPlayer* newplayer); + + /** + * Removes a player from the game + * + * Use removePlayer to send KGameMessage::IdRemovePlayer. As soon + * as this Id is received systemRemovePlayer is called and the player is + * removed directly. + **/ + void systemRemovePlayer(KPlayer* player,bool deleteit); + + /** + * This member function will transmit e.g. all players to that client, as well as + * all properties of these players (at least if they have been added by + * @ref KPlayer::addProperty) so that the client will finally have the same + * status as the master. You want to overwrite this function if you expand + * KGame by any properties which have to be known by all clients. + * + * Only the ADMIN is allowed to call this. + * @param clientID The ID of the message client which has connected + **/ + virtual void negotiateNetworkGame(Q_UINT32 clientID); + + /** + * syncronise the random numbers with all network clients + * not used by KGame - if it should be kept then as public method + */ + void syncRandom(); + + void deletePlayers(); + void deleteInactivePlayers(); + + /** + * @deprecated + * Use @ref KGameSequence instead. + * + * @param player the player who made the last move + * @return anything else but 0 is considered as game over + */ + virtual int checkGameOver(KPlayer *player); + + /** + * Load a saved game, from file OR network. Internal. + * Warning: loadgame must not rely that all players all already + * activated. Actually the network will activate a player AFTER + * the loadgame only. This is not true anymore. But be careful + * anyway. + * + * @param stream a data stream where you can stream the game from + * @param network is it a network call -> make players virtual + * @param reset shall the game be reset before loading + * + * @return true? + */ + virtual bool loadgame(QDataStream &stream, bool network, bool reset); + + /** + * Save a game, to file OR network. Internal. + * + * @param stream a data stream where you can stream the game from + * @param network is it a call from the network or from a file (unused but informative) + * @param saveplayers shall the players be saved too (should be TRUE) + * + * @return true? + */ + virtual bool savegame(QDataStream &stream, bool network,bool saveplayers); + +private: + //AB: this is to hide the "receiver" parameter from the user. It shouldn't be + //used if possible (except for init). + /** + * This is an overloaded function. Id differs from the public one only in + * its parameters: + * + * @param receiver The Client that will receive the message. You will hardly + * ever need this. It it internally used to initialize a newly connected + * client. + **/ + //void addPlayer(KPlayer* newplayer, Q_UINT32 receiver); + + /** + * Just the same as the public one except receiver: + * @param receiver 0 for broadcast, otherwise the receiver. Should only be + * used in special circumstances and not outside KGame. + **/ + bool removePlayer(KPlayer * player, Q_UINT32 receiver); + + /** + * Helping function - game negotiation + **/ + void setupGame(Q_UINT32 sender); + + /** + * Helping function - game negotiation + **/ + void setupGameContinue(QDataStream& msg, Q_UINT32 sender); + + /** + * Removes a player from all lists, removes the @ref KGame pointer from the + * @ref KPlayer and deletes the player. Used by (e.g.) @ref + * systemRemovePlayer + * @return True if the player has been removed, false if the current is not + * found + **/ + bool systemRemove(KPlayer* player,bool deleteit); + + +private: + KGamePrivate* d; +}; + +#endif diff --git a/libkdegames/kgame/kgamechat.cpp b/libkdegames/kgame/kgamechat.cpp new file mode 100644 index 00000000..16ec7c18 --- /dev/null +++ b/libkdegames/kgame/kgamechat.cpp @@ -0,0 +1,341 @@ +/* + This file is part of the KDE games library + Copyright (C) 2001-2002 Andreas Beckermann ([email protected]) + Copyright (C) 2001 Martin Heni ([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. +*/ + +#include "kgamechat.h" +#include "kgamechat.moc" + +#include "kgame.h" +#include "kplayer.h" +#include "kgameproperty.h" +#include "kgamemessage.h" + +#include <klocale.h> +#include <kdebug.h> + +#include <qmap.h> +#include <qintdict.h> + +//FIXME: +#define FIRST_ID 2 // first id, that is free of use, aka not defined above + +class KGameChatPrivate +{ +public: + KGameChatPrivate() + { + mFromPlayer = 0; + mGame = 0; + + mToMyGroup = -1; + } + + KGame* mGame; + KPlayer* mFromPlayer; + int mMessageId; + + + QIntDict<KPlayer> mIndex2Player; + + QMap<int, int> mSendId2PlayerId; + int mToMyGroup; // just as the above - but for the group, not for players +}; + +KGameChat::KGameChat(KGame* g, int msgid, QWidget* parent) : KChatBase(parent) +{ + init(g, msgid); +} + +KGameChat::KGameChat(KGame* g, int msgid, KPlayer* fromPlayer, QWidget* parent) : KChatBase(parent) +{ + init(g, msgid); + setFromPlayer(fromPlayer); +} + +KGameChat::KGameChat(QWidget* parent) : KChatBase(parent) +{ + init(0, -1); +} + +KGameChat::~KGameChat() +{ + kdDebug(11001) << k_funcinfo << endl; + delete d; +} + +void KGameChat::init(KGame* g, int msgId) +{ + kdDebug(11001) << k_funcinfo << endl; + d = new KGameChatPrivate; + setMessageId(msgId); + + setKGame(g); +} + +void KGameChat::addMessage(int fromId, const QString& text) +{ + if (!d->mGame) { + kdWarning(11001) << "no KGame object has been set" << endl; + addMessage(i18n("Player %1").arg(fromId), text); + } else { + KPlayer* p = d->mGame->findPlayer(fromId); + if (p) { + kdDebug(11001) << "adding message of player " << p->name() << "id=" << fromId << endl; + addMessage(p->name(), text); + } else { + kdWarning(11001) << "Could not find player id " << fromId << endl; + addMessage(i18n("Unknown"), text); + } + } +} + +void KGameChat::returnPressed(const QString& text) +{ + if (!d->mFromPlayer) { + kdWarning(11001) << k_funcinfo << ": You must set a player first!" << endl; + return; + } + if (!d->mGame) { + kdWarning(11001) << k_funcinfo << ": You must set a game first!" << endl; + return; + } + + kdDebug(11001) << "from: " << d->mFromPlayer->id() << "==" << d->mFromPlayer->name() << endl; + + int id = sendingEntry(); + + if (isToGroupMessage(id)) { + // note: there is currently no support for other groups than the players + // group! It might be useful to send to other groups, too + QString group = d->mFromPlayer->group(); + kdDebug(11001) << "send to group " << group << endl; + int sender = d->mFromPlayer->id(); + d->mGame->sendGroupMessage(text, messageId(), sender, group); + + //TODO + //AB: this message is never received!! we need to connect to + //KPlayer::networkData!!! + //TODO + + } else { + int toPlayer = 0; + if (!isSendToAllMessage(id) && isToPlayerMessage(id)) { + toPlayer = playerId(id); + if (toPlayer == -1) { + kdError(11001) << k_funcinfo << ": don't know that player " + << "- internal ERROR" << endl; + } + } + int receiver = toPlayer; + int sender = d->mFromPlayer->id(); + d->mGame->sendMessage(text, messageId(), receiver, sender); + } +} + +void KGameChat::setMessageId(int msgid) +{ d->mMessageId = msgid; } + +int KGameChat::messageId() const +{ return d->mMessageId; } + +bool KGameChat::isSendToAllMessage(int id) const +{ return (id == KChatBase::SendToAll); } + +bool KGameChat::isToGroupMessage(int id) const +{ return (id == d->mToMyGroup); } + +bool KGameChat::isToPlayerMessage(int id) const +{ +return d->mSendId2PlayerId.contains(id); } + +QString KGameChat::sendToPlayerEntry(const QString& name) const +{ return i18n("Send to %1").arg(name); } + +int KGameChat::playerId(int id) const +{ + if (!isToPlayerMessage(id)) { + return -1; + } + + return d->mSendId2PlayerId[id]; +} + +int KGameChat::sendingId(int playerId) const +{ + QMap<int, int>::Iterator it; + for (it = d->mSendId2PlayerId.begin(); it != d->mSendId2PlayerId.end(); ++it) { + if (it.data() == playerId) { + return it.key(); + } + } + return -1; +} + +const QString& KGameChat::fromName() const +{ return d->mFromPlayer ? d->mFromPlayer->name() : QString::null; } + +bool KGameChat::hasPlayer(int id) const +{ + return (sendingId(id) != -1); +} + +void KGameChat::setFromPlayer(KPlayer* p) +{ + if (!p) { + kdError(11001) << k_funcinfo << ": NULL player" << endl; + removeSendingEntry(d->mToMyGroup); + d->mFromPlayer = 0; + return; + } + if (d->mFromPlayer) { + changeSendingEntry(p->group(), d->mToMyGroup); + } else { + if (d->mToMyGroup != -1) { + kdWarning(11001) << "send to my group exists already - removing" << endl; + removeSendingEntry(d->mToMyGroup); + } + d->mToMyGroup = nextId(); + addSendingEntry(i18n("Send to My Group (\"%1\")").arg(p->group()), d->mToMyGroup); + } + d->mFromPlayer = p; + kdDebug(11001) << k_funcinfo << " player=" << p << endl; +} + + +void KGameChat::setKGame(KGame* g) +{ + if (d->mGame) { + slotUnsetKGame(); + } + kdDebug(11001) << k_funcinfo << " game=" << g << endl; + d->mGame = g; + + if (d->mGame) { + connect(d->mGame, SIGNAL(signalPlayerJoinedGame(KPlayer*)), + this, SLOT(slotAddPlayer(KPlayer*))); + connect(d->mGame, SIGNAL(signalPlayerLeftGame(KPlayer*)), + this, SLOT(slotRemovePlayer(KPlayer*))); + connect(d->mGame, SIGNAL(signalNetworkData(int, const QByteArray&, Q_UINT32, Q_UINT32)), + this, SLOT(slotReceiveMessage(int, const QByteArray&, Q_UINT32, Q_UINT32))); + connect(d->mGame, SIGNAL(destroyed()), this, SLOT(slotUnsetKGame())); + + QPtrList<KPlayer> playerList = *d->mGame->playerList(); + for (int unsigned i = 0; i < playerList.count(); i++) { + slotAddPlayer(playerList.at(i)); + } + } +} + +KGame* KGameChat::game() const +{ + return d->mGame; +} + +KPlayer* KGameChat::fromPlayer() const +{ + return d->mFromPlayer; +} + +void KGameChat::slotUnsetKGame() +{ +//TODO: test this method! + + if (!d->mGame) { + return; + } + disconnect(d->mGame, 0, this, 0); + removeSendingEntry(d->mToMyGroup); + QMap<int, int>::Iterator it; + for (it = d->mSendId2PlayerId.begin(); it != d->mSendId2PlayerId.end(); ++it) { + removeSendingEntry(it.data()); + } +} + +void KGameChat::slotAddPlayer(KPlayer* p) +{ + if (!p) { + kdError(11001) << k_funcinfo << ": cannot add NULL player" << endl; + return; + } + if (hasPlayer(p->id())) { + kdError(11001) << k_funcinfo << ": player was added before" << endl; + return; + } + + int sendingId = nextId(); + addSendingEntry(comboBoxItem(p->name()), sendingId); + d->mSendId2PlayerId.insert(sendingId, p->id()); + connect(p, SIGNAL(signalPropertyChanged(KGamePropertyBase*, KPlayer*)), + this, SLOT(slotPropertyChanged(KGamePropertyBase*, KPlayer*))); + connect(p, SIGNAL(signalNetworkData(int, const QByteArray&, Q_UINT32, KPlayer*)), + this, SLOT(slotReceivePrivateMessage(int, const QByteArray&, Q_UINT32, KPlayer*))); +} + +void KGameChat::slotRemovePlayer(KPlayer* p) +{ + if (!p) { + kdError(11001) << k_funcinfo << ": NULL player" << endl; + return; + } + if (!hasPlayer(p->id())) { + kdError(11001) << k_funcinfo << ": cannot remove non-existent player" << endl; + return; + } + + int id = sendingId(p->id()); + removeSendingEntry(id); + p->disconnect(this); + d->mSendId2PlayerId.remove(id); +} + +void KGameChat::slotPropertyChanged(KGamePropertyBase* prop, KPlayer* player) +{ + if (prop->id() == KGamePropertyBase::IdName) { +// kdDebug(11001) << "new Name" << endl; + changeSendingEntry(player->name(), sendingId(player->id())); +/* + mCombo->changeItem(comboBoxItem(player->name()), index); + */ + } else if (prop->id() == KGamePropertyBase::IdGroup) { + //TODO + } +} + +void KGameChat::slotReceivePrivateMessage(int msgid, const QByteArray& buffer, Q_UINT32 sender, KPlayer* me) +{ + if (!me || me != fromPlayer()) { + kdDebug() << k_funcinfo << "nope - not for us!" << endl; + return; + } + slotReceiveMessage(msgid, buffer, me->id(), sender); +} + +void KGameChat::slotReceiveMessage(int msgid, const QByteArray& buffer, Q_UINT32 , Q_UINT32 sender) +{ + QDataStream msg(buffer, IO_ReadOnly); + if (msgid != messageId()) { + return; + } + + QString text; + msg >> text; + + addMessage(sender, text); +} + diff --git a/libkdegames/kgame/kgamechat.h b/libkdegames/kgame/kgamechat.h new file mode 100644 index 00000000..6f7ea65d --- /dev/null +++ b/libkdegames/kgame/kgamechat.h @@ -0,0 +1,223 @@ +/* + This file is part of the KDE games library + Copyright (C) 2001-2002 Andreas Beckermann ([email protected]) + Copyright (C) 2001 Martin Heni ([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. +*/ + +#ifndef __KGAMECHAT_H__ +#define __KGAMECHAT_H__ + +#include <qstring.h> + +#include "kchatbase.h" +#include <kdemacros.h> +class KPlayer; +class KGame; +class KGamePropertyBase; + +class KGameChatPrivate; + +/** + * @short A Chat widget for KGame-based games + * + * Call @ref setFromPlayer() first - this will be used as the "from" part of + * every message you will send. Otherwise it won't work! You can also use the + * fromPlayer parameter in the constructor though... + * + * @author Andreas Beckermann <[email protected]> + **/ +class KDE_EXPORT KGameChat : public KChatBase +{ + Q_OBJECT +public: + /** + * Construct a @ref KGame chat widget on @p game that used @p msgid for + * the chat message. The @p fromPlayer is the local player (see @ref + * setFromPlayer). + **/ + KGameChat(KGame* game, int msgid, KPlayer* fromPlayer, QWidget * parent); + + /** + * @overload + * To make use of this widget you need to call @ref setFromPlayer + * manually. + **/ + KGameChat(KGame* game, int msgId, QWidget* parent); + + /** + * @overload + * This constructs a widget that is not usable. You must call at least + * setGame, setFromPlayer and setMessageId manually. + * @since 3.2 + **/ + KGameChat(QWidget* parent); + + virtual ~KGameChat(); + + enum SendingIds { + SendToGroup = 1 + }; + + /** + * This sets the fromPlayer to @p player. The fromPlayer is the + * player that will appear as "from" when you send messages through this + * widget. + * @param player The player of this widget + **/ + void setFromPlayer(KPlayer* player); + + KPlayer* fromPlayer() const; + + /** + * Set the @ref KGame object for this chat widget. All messages will be + * sent through this object. You don't have to implement any send + * functions, just call this function, call @ref setFromPlayer and be + * done :-) + * @param g The @ref KGame object the messages will be sent through + **/ + void setKGame(KGame* g); + + KGame* game() const; + + /** + * @return The id of the messages produced by KGameChat. The id will be + * used in @ref KGame as parameter msgid in the method @ref KGame::sendMessage + **/ + int messageId() const; + + /** + * Change the message id of the chat widget. It is recommended that you + * don't use this but prefer the constructor instead, but in certain + * situations (such as using this widget in Qt designer) it may be + * useful to change the message id. + * + * See also @ref messageId + * @since 3.2 + **/ + void setMessageId(int msgid); + + /** + * reimplemented from @ref KChatBase + * @return @ref KPlayer::name() for the player set by @ref setFromPlayer + **/ + virtual const QString& fromName() const; + + +public slots: + virtual void addMessage(const QString& fromName, const QString& text) { KChatBase::addMessage(fromName, text);} + virtual void addMessage(int fromId, const QString& text); + + void slotReceiveMessage(int, const QByteArray&, Q_UINT32 receiver, Q_UINT32 sender); + +protected: + /** + * @param id The ID of the sending entry, as returned by @ref + * KChatBase::sendingEntry + * @return True if the entry "send to all" was selected, otherwise false + **/ + bool isSendToAllMessage(int id) const; + + /** + * Used to indicate whether a message shall be sent to a group of + * players. Note that this was not yet implemented when this doc was + * written so this description might be wrong. (FIXME) + * @param id The ID of the sending entry, as returned by @ref + * KChatBase::sendingEntry + * @return True if the message is meant to be sent to a group (see @ref + * KPlayer::group), e.g. if "send to my group" was selected. + **/ + bool isToGroupMessage(int id) const; + + + /** + * Used to indicate whether the message shall be sent to a single player + * only. Note that you can also call @ref isSendToAllMessage and @ref + * isToGroupMessage - if both return false it must be a player message. + * This behaviour might be changed later - so don't depend on it. + * + * See also toPlayerId + * @param id The ID of the sending entry, as returned by + * KChatBase::sendingEntry + * @return True if the message shall be sent to a special player, + * otherwise false. + **/ + bool isToPlayerMessage(int id) const; + + /** + * @param id The ID of the sending entry, as returned by + * KChatBase::sendingEntry + * @return The ID of the player (see KPlayer::id) the sending entry + * belongs to. Note that the parameter id is an id as returned by ref + * KChatBase::sendingEntry and the id this method returns is a + * KPlayer ID. If isToPlayerMessage returns false this method + * returns -1 + **/ + int playerId(int id) const; + + /** + * @param playerId The ID of the KPlayer object + * @return The ID of the sending entry (see KChatBase) or -1 if + * the player id was not found. + **/ + int sendingId(int playerId) const; + + /** + * @return True if the player with this ID was added before (see + * slotAddPlayer) + **/ + bool hasPlayer(int id) const; + + /** + * @param name The name of the added player + * @return A string that will be added as sending entry in @ref + * KChatBase. By default this is "send to name" where name is the name + * that you specify. See also KChatBase::addSendingEntry + **/ + virtual QString sendToPlayerEntry(const QString& name) const; + + +protected slots: + /** + * Unsets a KGame object that has been set using setKGame + * before. You don't have to call this - this is usually done + * automatically. + **/ + void slotUnsetKGame(); + + + void slotPropertyChanged(KGamePropertyBase*, KPlayer*); + void slotAddPlayer(KPlayer*); + void slotRemovePlayer(KPlayer*); + + /** + * Called when KPlayer::signalNetworkData is emitted. The message + * gets forwarded to slotReceiveMessage if @p me equals + * fromPlayer. + **/ + void slotReceivePrivateMessage(int msgid, const QByteArray& buffer, Q_UINT32 sender, KPlayer* me); + +protected: + virtual void returnPressed(const QString& text); + +private: + void init(KGame* g, int msgid); + +private: + KGameChatPrivate* d; +}; + +#endif diff --git a/libkdegames/kgame/kgameerror.cpp b/libkdegames/kgame/kgameerror.cpp new file mode 100644 index 00000000..93f40f93 --- /dev/null +++ b/libkdegames/kgame/kgameerror.cpp @@ -0,0 +1,80 @@ +/* + This file is part of the KDE games library + Copyright (C) 2001 Andreas Beckermann ([email protected]) + Copyright (C) 2001 Martin Heni ([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 "kgameerror.h" +#include "kgamemessage.h" + +#include <klocale.h> + +QByteArray KGameError::errVersion(int remoteVersion) +{ + QByteArray b; + QDataStream s(b, IO_WriteOnly); + s << (Q_INT32)KGameMessage::version(); + s << (Q_INT32)remoteVersion; + return b; +} + +QByteArray KGameError::errCookie(int localCookie, int remoteCookie) +{ + QByteArray b; + QDataStream s(b, IO_WriteOnly); + s << (Q_INT32)localCookie; + s << (Q_INT32)remoteCookie; + return b; +} + +QString KGameError::errorText(int errorCode, const QByteArray& message) +{ + QDataStream s(message, IO_ReadOnly); + return errorText(errorCode, s); +} + +QString KGameError::errorText(int errorCode, QDataStream& s) +{ + QString text; + switch (errorCode) { + case Cookie: + { + Q_INT32 cookie1; + Q_INT32 cookie2; + s >> cookie1; + s >> cookie2; + text = i18n("Cookie mismatch!\nExpected Cookie: %1\nReceived Cookie: %2").arg(cookie1).arg(cookie2); + break; + } + case Version: + { + Q_INT32 version1; + Q_INT32 version2; + s >> version1; + s >> version2; + text = i18n("KGame Version mismatch!\nExpected Version: %1\nReceived Version: %2\n").arg(version1).arg(version2); + break; + } + default: + text = i18n("Unknown error code %1").arg(errorCode); + } + return text; +} + diff --git a/libkdegames/kgame/kgameerror.h b/libkdegames/kgame/kgameerror.h new file mode 100644 index 00000000..2916e891 --- /dev/null +++ b/libkdegames/kgame/kgameerror.h @@ -0,0 +1,59 @@ +/* + This file is part of the KDE games library + Copyright (C) 2001 Andreas Beckermann ([email protected]) + Copyright (C) 2001 Martin Heni ([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$ +*/ +#ifndef __KGAMEERROR_H_ +#define __KGAMEERROR_H_ + +#include <qstring.h> + + +class KGameError +{ +public: + KGameError() { } + ~KGameError() { } + + enum ErrorCodes { + Cookie = 0, // Cookie mismatch + Version = 1 // Version mismatch + }; + + /** + * Generate an error message with Erorr Code = ErrCookie + **/ + static QByteArray errCookie(int localCookie, int remoteCookie); + static QByteArray errVersion(int remoteVersion); + + /** + * Create an erorr text using a QDataStream (QByteArray) which was + * created using @ref KGameError. This is the opposite function to all + * the errXYZ() function (e.g. @ref errVersion). + * You want to use this to generate the message that shall be + * displayed to the user. + * @return an error message + **/ + static QString errorText(int errorCode, QDataStream& message); + static QString errorText(int errorCode, const QByteArray& message); + +}; + +#endif diff --git a/libkdegames/kgame/kgameio.cpp b/libkdegames/kgame/kgameio.cpp new file mode 100644 index 00000000..9b3a55e8 --- /dev/null +++ b/libkdegames/kgame/kgameio.cpp @@ -0,0 +1,539 @@ +/* + 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 "kgameio.h" +#include "kgameio.moc" +#include "kgame.h" +#include "kplayer.h" +#include "kgamemessage.h" +#include "kmessageio.h" + +#include <kdebug.h> + +#include <qwidget.h> +#include <qbuffer.h> +#include <qtimer.h> + +#include <stdlib.h> + +// ----------------------- Generic IO ------------------------- +KGameIO::KGameIO() : QObject(0,0) +{ + kdDebug(11001) << k_funcinfo << ": this=" << this << ", sizeof(this)" << sizeof(KGameIO) << endl; + mPlayer = 0; +} + +KGameIO::KGameIO(KPlayer* player) : QObject(0,0) +{ + kdDebug(11001) << k_funcinfo << ": this=" << this << ", sizeof(this)" << sizeof(KGameIO) << endl; + mPlayer = 0; + if (player) + { + player->addGameIO(this); + } +} + +KGameIO::~KGameIO() +{ + kdDebug(11001) << k_funcinfo << ": this=" << this << endl; + // unregister ourselves + if (player()) + { + player()->removeGameIO(this, false); + } +} + +void KGameIO::initIO(KPlayer *p) +{ + setPlayer(p); +} + +void KGameIO::notifyTurn(bool b) +{ + if (!player()) + { + kdWarning(11001) << k_funcinfo << ": player() is NULL" << endl; + return; + } + bool sendit=false; + QByteArray buffer; + QDataStream stream(buffer, IO_WriteOnly); + emit signalPrepareTurn(stream, b, this, &sendit); + if (sendit) + { + QDataStream ostream(buffer,IO_ReadOnly); + Q_UINT32 sender = player()->id(); // force correct sender + kdDebug(11001) << "Prepare turn sendInput" << endl; + sendInput(ostream, true, sender); + } +} + +KGame* KGameIO::game() const +{ + if (!player()) + { + return 0; + } + return player()->game(); +} + +bool KGameIO::sendInput(QDataStream& s, bool transmit, Q_UINT32 sender) +{ + if (!player()) + { + return false; + } + return player()->forwardInput(s, transmit, sender); +} + +void KGameIO::Debug() +{ + kdDebug(11001) << "------------------- KGAMEINPUT --------------------" << endl; + kdDebug(11001) << "this: " << this << endl; + kdDebug(11001) << "rtti : " << rtti() << endl; + kdDebug(11001) << "Player: " << player() << endl; + kdDebug(11001) << "---------------------------------------------------" << endl; +} + + +// ----------------------- Key IO --------------------------- +KGameKeyIO::KGameKeyIO(QWidget *parent) + : KGameIO() +{ + if (parent) + { + kdDebug(11001) << "Key Event filter installed" << endl; + parent->installEventFilter(this); + } +} + +KGameKeyIO::~KGameKeyIO() +{ + if (parent()) + { + parent()->removeEventFilter(this); + } +} + +int KGameKeyIO::rtti() const { return KeyIO; } + +bool KGameKeyIO::eventFilter( QObject *o, QEvent *e ) +{ + if (!player()) + { + return false; + } + + // key press/release + if ( e->type() == QEvent::KeyPress || + e->type() == QEvent::KeyRelease ) + { + QKeyEvent *k = (QKeyEvent*)e; + // kdDebug(11001) << "KGameKeyIO " << this << " key press/release " << k->key() << endl ; + QByteArray buffer; + QDataStream stream(buffer,IO_WriteOnly); + bool eatevent=false; + emit signalKeyEvent(this,stream,k,&eatevent); + QDataStream msg(buffer,IO_ReadOnly); + + if (eatevent && sendInput(msg)) + { + return eatevent; + } + return false; // do not eat otherwise + } + return QObject::eventFilter( o, e ); // standard event processing +} + + +// ----------------------- Mouse IO --------------------------- +KGameMouseIO::KGameMouseIO(QWidget *parent,bool trackmouse) + : KGameIO() +{ + if (parent) + { + kdDebug(11001) << "Mouse Event filter installed tracking=" << trackmouse << endl; + parent->installEventFilter(this); + parent->setMouseTracking(trackmouse); + } +} + +KGameMouseIO::~KGameMouseIO() +{ + if (parent()) + { + parent()->removeEventFilter(this); + } +} + +int KGameMouseIO::rtti() const +{ + return MouseIO; +} + +void KGameMouseIO::setMouseTracking(bool b) +{ + if (parent()) + { + ((QWidget*)parent())->setMouseTracking(b); + } +} + +bool KGameMouseIO::eventFilter( QObject *o, QEvent *e ) +{ + if (!player()) + { + return false; + } +// kdDebug(11001) << "KGameMouseIO " << this << endl ; + + // mouse action + if ( e->type() == QEvent::MouseButtonPress || + e->type() == QEvent::MouseButtonRelease || + e->type() == QEvent::MouseButtonDblClick || + e->type() == QEvent::Wheel || + e->type() == QEvent::MouseMove + ) + { + QMouseEvent *k = (QMouseEvent*)e; + // kdDebug(11001) << "KGameMouseIO " << this << endl ; + QByteArray buffer; + QDataStream stream(buffer,IO_WriteOnly); + bool eatevent=false; + emit signalMouseEvent(this,stream,k,&eatevent); +// kdDebug(11001) << "################# eatevent=" << eatevent << endl; + QDataStream msg(buffer,IO_ReadOnly); + if (eatevent && sendInput(msg)) + { + return eatevent; + } + return false; // do not eat otherwise + } + return QObject::eventFilter( o, e ); // standard event processing +} + + +// ----------------------- KGameProcesPrivate --------------------------- +class KGameProcessIO::KGameProcessIOPrivate +{ +public: + KGameProcessIOPrivate() + { + //mMessageServer = 0; + //mMessageClient = 0; + mProcessIO=0; + } + //KMessageServer *mMessageServer; + //KMessageClient *mMessageClient; + KMessageProcess *mProcessIO; +}; + +// ----------------------- Process IO --------------------------- +KGameProcessIO::KGameProcessIO(const QString& name) + : KGameIO() +{ + kdDebug(11001) << k_funcinfo << ": this=" << this << ", sizeof(this)=" << sizeof(KGameProcessIO) << endl; + d = new KGameProcessIOPrivate; + + //kdDebug(11001) << "================= KMEssageServer ==================== " << endl; + //d->mMessageServer=new KMessageServer(0,this); + //kdDebug(11001) << "================= KMEssageClient ==================== " << endl; + //d->mMessageClient=new KMessageClient(this); + kdDebug(11001) << "================= KMEssageProcessIO ==================== " << endl; + d->mProcessIO=new KMessageProcess(this,name); + kdDebug(11001) << "================= KMEssage Add client ==================== " << endl; + //d->mMessageServer->addClient(d->mProcessIO); + //kdDebug(11001) << "================= KMEssage SetSErver ==================== " << endl; + //d->mMessageClient->setServer(d->mMessageServer); + kdDebug(11001) << "================= KMEssage: Connect ==================== " << endl; + //connect(d->mMessageClient, SIGNAL(broadcastReceived(const QByteArray&, Q_UINT32)), + // this, SLOT(clientMessage(const QByteArray&, Q_UINT32))); + //connect(d->mMessageClient, SIGNAL(forwardReceived(const QByteArray&, Q_UINT32, const QValueList <Q_UINT32> &)), + // this, SLOT(clientMessage(const QByteArray&, Q_UINT32, const QValueList <Q_UINT32> &))); + connect(d->mProcessIO, SIGNAL(received(const QByteArray&)), + this, SLOT(receivedMessage(const QByteArray&))); + //kdDebug(11001) << "Our client is id="<<d->mMessageClient->id() << endl; +} + +KGameProcessIO::~KGameProcessIO() +{ + kdDebug(11001) << k_funcinfo << ": this=" << this << endl; + kdDebug(11001) << "player="<<player() << endl; + if (player()) + { + player()->removeGameIO(this,false); + } + if (d->mProcessIO) + { + delete d->mProcessIO; + d->mProcessIO=0; + } + delete d; +} + +int KGameProcessIO::rtti() const +{ + return ProcessIO; +} + +void KGameProcessIO::initIO(KPlayer *p) +{ + KGameIO::initIO(p); + // Send 'hello' to process + QByteArray buffer; + QDataStream stream(buffer, IO_WriteOnly); + Q_INT16 id = p->userId(); + stream << id; + + bool sendit=true; + if (p) + { + emit signalIOAdded(this,stream,p,&sendit); + if (sendit ) + { + Q_UINT32 sender = p->id(); + kdDebug(11001) << "Sending IOAdded to process player !!!!!!!!!!!!!! " << endl; + sendSystemMessage(stream, KGameMessage::IdIOAdded, 0, sender); + } + } +} + +void KGameProcessIO::notifyTurn(bool b) +{ + if (!player()) + { + kdWarning(11001) << k_funcinfo << ": player() is NULL" << endl; + return; + } + bool sendit=true; + QByteArray buffer; + QDataStream stream(buffer,IO_WriteOnly); + stream << (Q_INT8)b; + emit signalPrepareTurn(stream,b,this,&sendit); + if (sendit) + { + Q_UINT32 sender=player()->id(); + kdDebug(11001) << "Sending Turn to process player !!!!!!!!!!!!!! " << endl; + sendSystemMessage(stream, KGameMessage::IdTurn, 0, sender); + } +} + +void KGameProcessIO::sendSystemMessage(QDataStream &stream,int msgid, Q_UINT32 receiver, Q_UINT32 sender) +{ + sendAllMessages(stream, msgid, receiver, sender, false); +} + +void KGameProcessIO::sendMessage(QDataStream &stream,int msgid, Q_UINT32 receiver, Q_UINT32 sender) +{ + sendAllMessages(stream, msgid, receiver, sender, true); +} + +void KGameProcessIO::sendAllMessages(QDataStream &stream,int msgid, Q_UINT32 receiver, Q_UINT32 sender, bool usermsg) +{ + kdDebug(11001) << "==============> KGameProcessIO::sendMessage (usermsg="<<usermsg<<")" << endl; + // if (!player()) return ; + //if (!player()->isActive()) return ; + + if (usermsg) + { + msgid+=KGameMessage::IdUser; + } + + kdDebug(11001) << "=============* ProcessIO (" << msgid << "," << receiver << "," << sender << ") ===========" << endl; + + QByteArray buffer; + QDataStream ostream(buffer,IO_WriteOnly); + QBuffer *device=(QBuffer *)stream.device(); + QByteArray data=device->buffer();; + + KGameMessage::createHeader(ostream,sender,receiver,msgid); + // ostream.writeRawBytes(data.data()+device->at(),data.size()-device->at()); + ostream.writeRawBytes(data.data(),data.size()); + kdDebug(11001) << " Adding user data from pos="<< device->at() <<" amount= " << data.size() << " byte " << endl; + //if (d->mMessageClient) d->mMessageClient->sendBroadcast(buffer); + if (d->mProcessIO) + { + d->mProcessIO->send(buffer); + } +} + +//void KGameProcessIO::clientMessage(const QByteArray& receiveBuffer, Q_UINT32 clientID, const QValueList <Q_UINT32> &recv) +void KGameProcessIO::receivedMessage(const QByteArray& receiveBuffer) +{ + QDataStream stream(receiveBuffer,IO_ReadOnly); + int msgid; + Q_UINT32 sender; + Q_UINT32 receiver; + KGameMessage::extractHeader(stream,sender,receiver,msgid); + + kdDebug(11001) << "************* Got process message sender =" << sender + << " receiver=" << receiver << " msgid=" << msgid << endl; + + + // Cut out the header part...to not confuse network code + QBuffer *buf=(QBuffer *)stream.device(); + QByteArray newbuffer; + newbuffer.setRawData(buf->buffer().data()+buf->at(),buf->size()-buf->at()); + QDataStream ostream(newbuffer,IO_ReadOnly); + kdDebug(11001) << "Newbuffer size=" << newbuffer.size() << endl; + + + +// This is a dummy message which allows us the process to talk with its owner + if (msgid==KGameMessage::IdProcessQuery) + { + emit signalProcessQuery(ostream,this); + } + else if (player()) + { + sender = player()->id(); // force correct sender + if (msgid==KGameMessage::IdPlayerInput) + { + sendInput(ostream,true,sender); + } + else + { + player()->forwardMessage(ostream,msgid,receiver,sender); + } + } + else + { + kdDebug(11001) << k_funcinfo << ": Got message from process but no player defined!" << endl; + } + newbuffer.resetRawData(buf->buffer().data()+buf->at(),buf->size()-buf->at()); +} + + +// ----------------------- Computer IO -------------------------- +class KGameComputerIO::KGameComputerIOPrivate +{ +//TODO: maybe these should be KGameProperties!! +public: + KGameComputerIOPrivate() + { + mAdvanceCounter = 0; + mReactionPeriod = 0; + + mPauseCounter = 0; + + mAdvanceTimer = 0; + } + int mAdvanceCounter; + int mReactionPeriod; + + int mPauseCounter; + + QTimer* mAdvanceTimer; +}; + +KGameComputerIO::KGameComputerIO() : KGameIO() +{ + init(); +} + +KGameComputerIO::KGameComputerIO(KPlayer* p) : KGameIO(p) +{ + init(); +} + +void KGameComputerIO::init() +{ + d = new KGameComputerIOPrivate; +} + +KGameComputerIO::~KGameComputerIO() +{ + if (d->mAdvanceTimer) + { + delete d->mAdvanceTimer; + } + delete d; +} + +int KGameComputerIO::rtti() const +{ + return ComputerIO; +} + +void KGameComputerIO::setReactionPeriod(int calls) +{ + d->mReactionPeriod = calls; +} + +int KGameComputerIO::reactionPeriod() const +{ + return d->mReactionPeriod; +} + +void KGameComputerIO::setAdvancePeriod(int ms) +{ + stopAdvancePeriod(); + d->mAdvanceTimer = new QTimer(this); + connect(d->mAdvanceTimer, SIGNAL(timeout()), this, SLOT(advance())); + d->mAdvanceTimer->start(ms); +} + +void KGameComputerIO::stopAdvancePeriod() +{ + if (d->mAdvanceTimer) + { + d->mAdvanceTimer->stop(); + delete d->mAdvanceTimer; + } +} + +void KGameComputerIO::pause(int calls) +{ + d->mPauseCounter = calls; +} + +void KGameComputerIO::unpause() +{ + pause(0); +} + +void KGameComputerIO::advance() +{ + if (d->mPauseCounter > 0) + { + d->mPauseCounter--; + return; + } + else if (d->mPauseCounter < 0) + { + return; + } + d->mAdvanceCounter++; + if (d->mAdvanceCounter >= d->mReactionPeriod) + { + d->mAdvanceCounter = 0; + reaction(); + } +} + +void KGameComputerIO::reaction() +{ + emit signalReaction(); +} + + diff --git a/libkdegames/kgame/kgameio.h b/libkdegames/kgame/kgameio.h new file mode 100644 index 00000000..4d7e0f5b --- /dev/null +++ b/libkdegames/kgame/kgameio.h @@ -0,0 +1,566 @@ +/* + 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$ +*/ +#ifndef __KGAMEIO_H__ +#define __KGAMEIO_H__ + +#include <qstring.h> +#include <qobject.h> +#include <kdemacros.h> +class KPlayer; +class KGame; +class KProcess; + +/** + * \short Base class for IO devices for games + * + * This is the master class for + * creating IO game devices. You cannot use it directly. + * Either take one of the classes derived from it or + * you have to create your own IO class derived from it (more probably). + * + * The idea behind this class is to provide a common interface + * for input devices into your game. By programming a KGameIO + * device you need not distinguish the actual IO in the game + * anymore. All work is done by the IO's. This allows very + * easy reuse in other games as well. + * A further advantage of using the IO's is that you can exchange + * the control of a player at runtime. E.g. you switch a player + * to be controlled by the computer or vice versa. + * + * To achieve this you have to make all of your player inputs through a + * KGameIO. You will usually call KGameIO::sendInput to do so. + * + * @author Martin Heni <[email protected]> + */ +class KDE_EXPORT KGameIO : public QObject +{ + Q_OBJECT + +public: + /** + * Constructs a KGameIO object + */ + KGameIO(); + KGameIO(KPlayer*); + virtual ~KGameIO(); + + /** + * Gives debug output of the game status + */ + void Debug(); + + /** + * Identifies the KGameIO via the rtti function + */ + enum IOMode {GenericIO=1,KeyIO=2,MouseIO=4,ProcessIO=8,ComputerIO=16}; + /** + * Run time idendification. Predefined values are from IOMode + * You MUST overwrite this in derived classes! + * + * @return rtti value + */ + virtual int rtti() const = 0; // Computer, network, local, ... + + /** + * This function returns the player who owns this IO + * + * @return the player this IO device is plugged into + */ + KPlayer *player() const {return mPlayer;} + + /** + * Equivalent to player()->game() + * @return the @ref KGame object of this player + **/ + KGame* game() const; + + /** + * Sets the player to which this IO belongs to. This + * is done automatically when adding a device to a + * player + * + * @param p the player + */ + void setPlayer(KPlayer *p) {mPlayer=p;} + + /** + * Init this device by setting the player and e.g. sending an + * init message to the device. This initialisation message is + * very useful for computer players as you can transmit the + * game status to them and only update this status in the setTurn + * commands. + * + * Called by @ref KPlayer::addGameIO only! + */ + virtual void initIO(KPlayer *p); + + /** + * Notifies the IO device that the player's setTurn had been called + * Called by KPlayer + * + * This emits @ref signalPrepareTurn and sends the turn if the send + * parameter is set to true. + * + * @param b turn is true/false + */ + virtual void notifyTurn(bool b); + + /** + * Send an input message using @ref KPlayer::forwardInput + **/ + bool sendInput(QDataStream& stream, bool transmit = true, Q_UINT32 sender = 0); + +signals: + /** + * Signal generated when @ref KPlayer::myTurn changes. This can either be + * when you get the turn status or when you lose it. + * + * The datastream has to be filled with a move. If you set (or leave) the + * send parameter to FALSE then nothing happens: the datastream will be + * ignored. If you set it to TRUE @ref sendInput is used to + * send the move. + * + * Often you want to ignore this signal (leave send=FALSE) and send the + * message later. This is usually the case for a human player as he probably + * doesn't react immediately. But you can still use this e.g. to notify the + * player about the turn change. + * + * Example: + * \code + * void GameWindow::slotPrepareTurn(QDataStream &stream,bool b,KGameIO *input,bool * ) + * { + * KPlayer *player=input->player(); + * if (!player->myTurn()) return ; + * if (!b) return ; // only do something on setTurn(true) + * stream << 1 << 2 << 3; // Some data for the process + * } + * \endcode + * + * @param io the KGameIO object itself + * @param stream the stream into which the move will be written + * @param turn the argument of setTurn + * @param send set this to true to send the generated move using @ref + * sendInput + **/ + void signalPrepareTurn(QDataStream & stream, bool turn, KGameIO *io, bool * send); + + +private: + KPlayer *mPlayer; +}; + +/** + * The KGameKeyIO class. It is used to process keyboard input + * from a widget and create moves for the player it belongs to. + * @author Martin Heni <[email protected]> + */ +class KDE_EXPORT KGameKeyIO : public KGameIO +{ + Q_OBJECT + +public: + /** + * Create a keyboard input devices. All keyboards + * inputs of the given widgets are passed through a signal + * handler signalKeyEvent and can be used to generate + * a valid move for the player. + * Note the widget you pass to the constructor must be + * the main window of your application, e.g. view->parentWidget() + * as QT does not forward your keyevents otherwise. This means + * that this might be a different widget comapred to the one you + * use for mouse inputs! + * Example: + * \code + * KGameKeyIO *input; + * input=new KGameKeyIO(myWidget); + * connect(input,SIGNAL(signalKeyEvent(KGameIO *,QDataStream &,QKeyEvent *,bool *)), + * this,SLOT(slotKeyInput(KGameIO *,QDataStream &,QKeyEvent *,bool *))); + * \endcode + * + * @param parent The parents widget whose keyboard events * should be grabbed + */ + KGameKeyIO(QWidget *parent); + virtual ~KGameKeyIO(); + + /** + * The idendification of the IO + * + * @return KeyIO + */ + virtual int rtti() const; + +signals: + /** + * Signal handler for keyboard events. This function is called + * on every keyboard event. If appropriate it can generate a + * move for the player the device belongs to. If this is done + * and the event is eaten eatevent needs to be set to true. + * What move you generate (i.e. what you write to the stream) + * is totally up to you as it will not be evaluated but forwared + * to the player's/game's input move function + * Example: + * \code + * KPlayer *player=input->player(); // Get the player + * Q_INT32 key=e->key(); + * stream << key; + * eatevent=true; + * \endcode + * + * @param io the IO device we belong to + * @param stream the stream where we write our move into + * @param m The QKeyEvent we can evaluate + * @param eatevent set this to true if we processed the event + */ + void signalKeyEvent(KGameIO *io,QDataStream &stream,QKeyEvent *m,bool *eatevent); + +protected: + /** + * Internal method to process the events + */ + bool eventFilter( QObject *o, QEvent *e ); +}; + +/** + * The KGameMouseIO class. It is used to process mouse input + * from a widget and create moves for the player it belongs to. + * @author Martin Heni <[email protected]> + */ +class KDE_EXPORT KGameMouseIO : public KGameIO +{ + Q_OBJECT + +public: + /** + * Creates a mouse IO device. It captures all mouse + * event of the given widget and forwards them to the + * signal handler signalMouseEvent. + * Example: + * \code + * KGameMouseIO *input; + * input=new KGameMouseIO(mView); + * connect(input,SIGNAL(signalMouseEvent(KGameIO *,QDataStream &,QMouseEvent *,bool *)), + * this,SLOT(slotMouseInput(KGameIO *,QDataStream &,QMouseEvent *,bool *))); + * \endcode + * + * @param parent The widget whose events should be captured + * @param trackmouse enables mouse tracking (gives mouse move events) + */ + KGameMouseIO(QWidget *parent,bool trackmouse=false); + virtual ~KGameMouseIO(); + + /** + * Manually activate or deactivate mouse tracking + * + * @param b true = tracking on + */ + void setMouseTracking(bool b); + /** + * The idendification of the IO + * + * @return MouseIO + */ + virtual int rtti() const; + +signals: + /** + * Signal handler for mouse events. This function is called + * on every mouse event. If appropriate it can generate a + * move for the player the device belongs to. If this is done + * and the event is eaten eatevent needs to be set to true. + * @see signalKeyEvent + * Example: + * \code + * KPlayer *player=input->player(); // Get the player + * Q_INT32 button=e->button(); + * stream << button; + * eatevent=true; + * \endcode + * + * @param io the IO device we belong to + * @param stream the stream where we write our move into + * @param m The QMouseEvent we can evaluate + * @param eatevent set this to true if we processed the event + */ + void signalMouseEvent(KGameIO *io,QDataStream &stream,QMouseEvent *m,bool *eatevent); + +protected: + /** + * Internal event filter + */ + bool eventFilter( QObject *o, QEvent *e ); + +}; + + +/** + * The KGameProcessIO class. It is used to create a computer player + * via a separate process and communicate transparetly with it. + * Its counterpart is the @ref KGameProcess class which needs + * to be used by the computer player. See its documentation + * for the definition of the computer player. + * @author Martin Heni <[email protected]> + */ +class KDE_EXPORT KGameProcessIO : public KGameIO +{ + Q_OBJECT + +public: + /** + * Creates a computer player via a separate process. The process + * name is given as fully qualified filename. + * Example: + * \code + * KGameProcessIO *input; + * input=new KGameProcessIO(executable_file); + * connect(input,SIGNAL(signalPrepareTurn(QDataStream &,bool,KGameIO *,bool *)), + * this,SLOT(slotPrepareTurn(QDataStream &,bool,KGameIO *,bool *))); + * connect(input,SIGNAL(signalProcessQuery(QDataStream &,KGameProcessIO *)), + * this,SLOT(slotProcessQuery(QDataStream &,KGameProcessIO *))); + * \endcode + * + * @param name the filename of the process to start + */ + KGameProcessIO(const QString& name); + + /** + * Deletes the process input devices + */ + virtual ~KGameProcessIO(); + + /** + * The idendification of the IO + * + * @return ProcessIO + */ + int rtti() const; + + /** + * Send a message to the process. This is analogous to the sendMessage + * commands of KGame. It will result in a signal of the computer player + * on which you can react in the process player. + * + * @param stream - the actual data + * @param msgid - the id of the message + * @param receiver - not used + * @param sender - who send the message + */ + void sendMessage(QDataStream &stream,int msgid, Q_UINT32 receiver, Q_UINT32 sender); + + /** + * Send a system message to the process. This is analogous to the sendMessage + * commands of KGame. It will result in a signal of the computer player + * on which you can react in the process player. + * + * @param stream - the actual data + * @param msgid - the id of the message + * @param receiver - not used + * @param sender - who send the message + */ + void sendSystemMessage(QDataStream &stream, int msgid, Q_UINT32 receiver, Q_UINT32 sender); + + /** + * Init this device by setting the player and e.g. sending an + * init message to the device. Calling this function will emit + * the IOAdded signal on which you can react and initilise the + * computer player. + * This function is called automatically when adding the IO to + * a player. + */ + void initIO(KPlayer *p); + + /** + * Notifies the IO device that the player's setTurn had been called + * Called by KPlayer. You can react on the @ref signalPrepareTurn to + * prepare a message for the process, i.e. either update it on + * the changes made to the game since the last turn or the initIO + * has been called or transmit your gamestatus now. + * + * @param turn is true/false + */ + virtual void notifyTurn(bool turn); + + protected: + /** + * Internal ~ombined function for all message handling + **/ + void sendAllMessages(QDataStream &stream,int msgid, Q_UINT32 receiver, Q_UINT32 sender, bool usermsg); + + protected slots: + /** + * Internal message handler to receive data from the process + */ + void receivedMessage(const QByteArray& receiveBuffer); + + +signals: + /** + * A computer query message is received. This is a 'dummy' + * message sent by the process if it needs to communicate + * with us. It is not forwarded over the network. + * Reacting to this message allows you to 'answer' questions + * of the process, e.g. sending addition data which the process + * needs to calculate a move. + * + * Example: + * \code + * void GameWindow::slotProcessQuery(QDataStream &stream,KGameProcessIO *reply) + * { + * int no; + * stream >> no; // We assume the process sends us an integer question numner + * if (no==1) // but YOU have to do this in the process player + * { + * QByteArray buffer; + * QDataStream out(buffer,IO_WriteOnly); + * reply->sendSystemMessage(out,4242,0,0); // lets reply something... + * } + * } + * \endcode + */ + void signalProcessQuery(QDataStream &stream,KGameProcessIO *me); + + /** + * Signal generated when the computer player is added. + * You can use this to communicated with the process and + * e.g. send initialisation information to the process. + * + * @param game the KGameIO object itself + * @param stream the stream into which the move will be written + * @param p the player itself + * @param send set this to false if no move should be generated + */ + void signalIOAdded(KGameIO *game,QDataStream &stream,KPlayer *p,bool *send); + + +protected: + +private: + class KGameProcessIOPrivate; + KGameProcessIOPrivate* d; +}; + +/** + * \brief KGameIO variant for real-time games + * + * The KGameComputerIO class. It is used to create a LOCAL computer player + * and communicate transparently with it. + * Question: Is this needed or is it overwritten anyway for a real game? + * + * You most probably don't want to use this if you want to design a turn based + * game/player. You'll rather use @ref KGameIO directly, i.e. subclass it + * yourself. You just need to use @ref KGameIO::signalPrepareTurn and/or @ref + * KGameIO::notifyTurn there. + * + * This is rather meant to be of use in real time games. + * + * @author <[email protected]> + */ +class KDE_EXPORT KGameComputerIO : public KGameIO +{ + Q_OBJECT + +public: + /** + * Creates a LOCAL computer player + * + */ + KGameComputerIO(); + KGameComputerIO(KPlayer* player); + ~KGameComputerIO(); + + int rtti() const; + + /** + * The number of advance calls until the player (or rather: the IO) + * does something (default: 1). + **/ + void setReactionPeriod(int advanceCalls); + int reactionPeriod() const; + + /** + * Start a QTimer which calls advance every @p ms milli seconds. + **/ + void setAdvancePeriod(int ms); + + void stopAdvancePeriod(); + + /** + * Ignore calls number of advance calls. if calls is -1 then all + * following advance calls are ignored until unpause is called. + * + * This simply prevents the internal advance counter to be increased. + * + * You may want to use this to emulate a "thinking" computer player. Note + * that this means if you increase the advance period (see + * setAdvancePeriod), i.e. if you change the speed of your game, your + * computer player thinks "faster". + * @param calls Number of advance calls to be ignored + **/ + void pause(int calls = -1); + + /** + * Equivalent to pause(0). Immediately continue to increase the internal + * advance counter. + **/ + void unpause(); + +public slots: + /** + * Works kind of similar to QCanvas::advance. Increase the internal + * advance counter. If @p reactionPeriod is reached the counter is set back to + * 0 and @ref signalReaction is emitted. This is when the player is meant + * to do something (move its units or so). + * + * This is very useful if you use QCanvas as you can use this in your + * QCanvas::advance call. The advantage is that if you change the speed + * of the game (i.e. change QCanvas::setAdvancePeriod) the computer + * player gets slower as well. + * + * If you don't use QCanvas you can use setAdvancePeriod to get + * the same result. Alternatively you can just use a QTimer. + * + **/ + virtual void advance(); + +signals: + /** + * This signal is emitted when your computer player is meant to do + * something, or better is meant to be allowed to do something. + **/ + void signalReaction(); + +protected: + /** + * Default implementation simply emits signalReaction + **/ + virtual void reaction(); + +private: + void init(); + +private: + class KGameComputerIOPrivate; + KGameComputerIOPrivate* d; +}; + + +#endif diff --git a/libkdegames/kgame/kgamemessage.cpp b/libkdegames/kgame/kgamemessage.cpp new file mode 100644 index 00000000..6464d407 --- /dev/null +++ b/libkdegames/kgame/kgamemessage.cpp @@ -0,0 +1,156 @@ +/* + 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 "kgamemessage.h" + +#include <klocale.h> + +#define MESSAGE_VERSION 2 + +Q_UINT32 KGameMessage::createPlayerId(int oldplayerid,Q_UINT32 gameid) +{ + int p; + p = oldplayerid & 0x3ff; // remove game id + p |= (gameid << 10); + return p; +} + +int KGameMessage::rawPlayerId(Q_UINT32 playerid) +{ + return playerid & 0x03ff; +} + +Q_UINT32 KGameMessage::rawGameId(Q_UINT32 playerid) +{ + return (playerid & 0xfc00) >> 10; +} + +bool KGameMessage::isPlayer(Q_UINT32 msgid) +{ + if (msgid & 0xfc00) { + return true; + } else { + return false; + } +} + +bool KGameMessage::isGame(Q_UINT32 msgid) +{ + return !isPlayer(msgid); +} + + +void KGameMessage::createHeader(QDataStream &msg,Q_UINT32 sender,Q_UINT32 receiver,int msgid) +{ + msg << (Q_INT16)sender << (Q_INT16)receiver << (Q_INT16)msgid; +} + +void KGameMessage::extractHeader(QDataStream &msg,Q_UINT32 &sender,Q_UINT32 &receiver,int &msgid) +{ + Q_INT16 d3,d4,d5; + msg >> d3 >> d4 >> d5; + sender=d3;receiver=d4;msgid=d5; +} + +void KGameMessage::createPropertyHeader(QDataStream &msg,int id) +{ + msg << (Q_INT16)id; +} + +void KGameMessage::extractPropertyHeader(QDataStream &msg,int &id) +{ + Q_INT16 d1; + msg >> d1; + id=d1; +} + +void KGameMessage::createPropertyCommand(QDataStream &msg,int cmdid,int pid,int cmd) +{ + createPropertyHeader(msg,cmdid); + msg << (Q_INT16)pid ; + msg << (Q_INT8)cmd ; +} + +void KGameMessage::extractPropertyCommand(QDataStream &msg,int &pid,int &cmd) +{ + Q_INT16 d1; + Q_INT8 d2; + msg >> d1 >> d2; + pid=d1; + cmd=d2; +} + +int KGameMessage::version() +{ + return MESSAGE_VERSION; +} + +QString KGameMessage::messageId2Text(int msgid) +{ +// this should contain all KGameMessage::GameMessageIds +// feel free to add missing ones, to remove obsolete one and even feel free to +// let it be ;-) + switch (msgid) { + case KGameMessage::IdSetupGame: + return i18n("Setup Game"); + case KGameMessage::IdSetupGameContinue: + return i18n("Setup Game Continue"); + case KGameMessage::IdGameLoad: + return i18n("Load Game"); + case KGameMessage::IdGameConnected: + return i18n("Client game connected"); + case KGameMessage::IdGameSetupDone: + return i18n("Game setup done"); + case KGameMessage::IdSyncRandom: + return i18n("Synchronize Random"); + case KGameMessage::IdDisconnect: + return i18n("Disconnect"); + case KGameMessage::IdPlayerProperty: + return i18n("Player Property"); + case KGameMessage::IdGameProperty: + return i18n("Game Property"); + case KGameMessage::IdAddPlayer: + return i18n("Add Player"); + case KGameMessage::IdRemovePlayer: + return i18n("Remove Player"); + case KGameMessage::IdActivatePlayer: + return i18n("Activate Player"); + case KGameMessage::IdInactivatePlayer: + return i18n("Inactivate Player"); + case KGameMessage::IdTurn: + return i18n("Id Turn"); + case KGameMessage::IdError: + return i18n("Error Message"); + case KGameMessage::IdPlayerInput: + return i18n("Player Input"); + case KGameMessage::IdIOAdded: + return i18n("An IO was added"); + case KGameMessage::IdProcessQuery: + return i18n("Process Query"); + case KGameMessage::IdPlayerId: + return i18n("Player ID"); + case KGameMessage::IdUser: // IdUser must be unknown for use, too! + default: + return QString::null; + } +} diff --git a/libkdegames/kgame/kgamemessage.h b/libkdegames/kgame/kgamemessage.h new file mode 100644 index 00000000..4394b4fa --- /dev/null +++ b/libkdegames/kgame/kgamemessage.h @@ -0,0 +1,173 @@ +/* + 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$ +*/ +#ifndef __KGAMEMSG_H_ +#define __KGAMEMSG_H_ + +#include <qdatastream.h> +#include <kdemacros.h> + +class KDE_EXPORT KGameMessage +{ + public: + /** + * Creates a fully qualified player ID which contains the original + * player id in the lower bits and the game number in the higher bits. + * Do not rely on the exact bit positions as they are internal. + * + * See also @ref rawPlayerId and @ref rawGameId which are the inverse + * operations + * + * @param playerid the player id - can include a gameid (will get removed) + * @param gameid The game id (<64). 0 For broadcast. + * @return the new player id + */ + static Q_UINT32 createPlayerId(int player, Q_UINT32 game); + + /** + * Returns the raw playerid, that is, a id which does not + * contain the game number encoded in it. See also @ref createPlayerId which + * is the inverse operation. + * + * @param the player id + * @return the raw player id + **/ + static int rawPlayerId(Q_UINT32 playerid); + + /** + * Returns the raw game id, that is, the game id the player + * belongs to. Se also @ref createPlayerId which is the inverse operation. + * + * @param the player id + * @return the raw game id + **/ + static Q_UINT32 rawGameId(Q_UINT32 playerid); + + /** + * Checks whether a message receiver/sender is a player + * + * @param id The ID of the sender/receiver + * @return true/false + */ + static bool isPlayer(Q_UINT32 id); + + /** + * Checks whether the sender/receiver of a message is a game + * + * @param id The ID of the sender/receiver + * @return true/false + */ + static bool isGame(Q_UINT32 id); + + /** + * Creates a message header given cookie,sender,receiver,... + * + * Also puts "hidden" header into the stream which are used by KGameClient + * (message length and magic cookie). If you don't need them remove them + * with @ref dropExternalHeader + */ + static void createHeader(QDataStream &msg, Q_UINT32 sender, Q_UINT32 receiver, int msgid); + + /** + * Retrieves the information like cookie,sender,receiver,... from a message header + * + * Note that it could be necessary to call @ref dropExternalHeader first + */ + static void extractHeader(QDataStream &msg,Q_UINT32 &sender, Q_UINT32 &receiver, int &msgid); + + /** + * Creates a property header given the property id + */ + static void createPropertyHeader(QDataStream &msg, int id); + + /** + * Retrieves the property id from a property message header + */ + static void extractPropertyHeader(QDataStream &msg, int &id); + + /** + * Creates a property header given the property id + */ + static void createPropertyCommand(QDataStream &msg, int cmdid, int pid, int cmd); + + /** + * Retrieves the property id from a property message header + */ + static void extractPropertyCommand(QDataStream &msg, int &pid, int &cmd); + + /** + * @return Version of the network library + */ + static int version(); + + /** + * This function takes a @ref GameMessageIds as argument and returns a + * suitable string for it. This string can't be used to identify a message + * (as it is i18n'ed) but it can make debugging more easy. See also @ref + * KGameDebugDialog. + * @return Either a i18n'ed string (the name of the id) or QString::null if + * the msgid is unknown + **/ + static QString messageId2Text(int msgid); + + + /** + * Message Ids used inside @ref KGame. + * + * You can use your own custom message Id by adding @p IdUser to it. + **/ +// please document every new id with a short comment + enum GameMessageIds { +// game init, game load, disconnect, ... + IdSetupGame=1, // sent to a newly connected player + IdSetupGameContinue=2, // continue the setup + IdGameLoad=3, // load/save the game to the client + IdGameConnected=4, // Client successfully connected to master + IdSyncRandom=5, // new random seed set - sync games + IdDisconnect=6, // KGame object disconnects from game + IdGameSetupDone=7, // New game client is now operational + +// properties + IdPlayerProperty=20, // a player property changed + IdGameProperty=21, // a game property changed + +// player management + IdAddPlayer=30, // add a player + IdRemovePlayer=31, // the player will be removed + IdActivatePlayer=32, // Activate a player + IdInactivatePlayer=33, // Inactivate a player + IdTurn=34, // Turn to be prepared + +// to-be-categorized + IdError=100, // an error occurred + IdPlayerInput=101, // a player input occurred + IdIOAdded=102, // KGameIO got added to a player...init this IO + +// special ids for computer player + IdProcessQuery=220, // Process queries data (process only) + IdPlayerId=221, // PlayerId got changed (process only) + + IdUser=256 // a user specified message + }; +}; + +#endif diff --git a/libkdegames/kgame/kgamenetwork.cpp b/libkdegames/kgame/kgamenetwork.cpp new file mode 100644 index 00000000..9eccb868 --- /dev/null +++ b/libkdegames/kgame/kgamenetwork.cpp @@ -0,0 +1,516 @@ +/* + 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 "kgamenetwork.h" +#include "kgamenetwork.moc" +#include "kgamemessage.h" +#include "kgameerror.h" + +#include "kmessageserver.h" +#include "kmessageclient.h" +#include "kmessageio.h" +#include <dnssd/publicservice.h> + +#include <kdebug.h> + +#include <qbuffer.h> + + +class KGameNetworkPrivate +{ +public: + KGameNetworkPrivate() + { + mMessageClient = 0; + mMessageServer = 0; + mDisconnectId = 0; + mService = 0; + } + +public: + KMessageClient* mMessageClient; + KMessageServer* mMessageServer; + Q_UINT32 mDisconnectId; // Stores gameId() over a disconnect process + DNSSD::PublicService* mService; + QString mType; + QString mName; + + int mCookie; +}; + +// ------------------- NETWORK GAME ------------------------ +KGameNetwork::KGameNetwork(int c, QObject* parent) : QObject(parent, 0) +{ + d = new KGameNetworkPrivate; + d->mCookie = (Q_INT16)c; + + // Init the game as a local game, i.e. + // create your own KMessageServer and a KMessageClient connected to it. + setMaster(); + + kdDebug(11001) << k_funcinfo << "this=" << this <<", cookie=" << cookie() << " sizeof(this)="<<sizeof(KGameNetwork) << endl; +} + +KGameNetwork::~KGameNetwork() +{ + kdDebug(11001) << k_funcinfo << "this=" << this << endl; +// Debug(); + delete d->mService; + delete d; +} + +// ----------------------------- status methods +bool KGameNetwork::isNetwork() const +{ return isOfferingConnections() || d->mMessageClient->isNetwork();} + +Q_UINT32 KGameNetwork::gameId() const +{ + //return d->mMessageClient->id() ; + // Return stored id in the case of disconnect. In any other + // case the disconnect id is 0 + if (d->mMessageClient->id()!=0 ) { + return d->mMessageClient->id() ; + } else { + return d->mDisconnectId; + } +} + +int KGameNetwork::cookie() const +{ return d->mCookie; } + +bool KGameNetwork::isMaster() const +{ return (d->mMessageServer != 0); } + +bool KGameNetwork::isAdmin() const +{ return (d->mMessageClient->isAdmin()); } + +KMessageClient* KGameNetwork::messageClient() const +{ return d->mMessageClient; } + +KMessageServer* KGameNetwork::messageServer() const +{ return d->mMessageServer; } + +// ----------------------- network init +void KGameNetwork::setMaster() +{ + if (!d->mMessageServer) { + d->mMessageServer = new KMessageServer (cookie(), this); + } else { + kdWarning(11001) << k_funcinfo << "Server already running!!" << endl; + } + if (!d->mMessageClient) { + d->mMessageClient = new KMessageClient (this); + connect (d->mMessageClient, SIGNAL(broadcastReceived(const QByteArray&, Q_UINT32)), + this, SLOT(receiveNetworkTransmission(const QByteArray&, Q_UINT32))); + connect (d->mMessageClient, SIGNAL(connectionBroken()), + this, SIGNAL(signalConnectionBroken())); + connect (d->mMessageClient, SIGNAL(aboutToDisconnect(Q_UINT32)), + this, SLOT(aboutToLoseConnection(Q_UINT32))); + connect (d->mMessageClient, SIGNAL(connectionBroken()), + this, SLOT(slotResetConnection())); + + connect (d->mMessageClient, SIGNAL(adminStatusChanged(bool)), + this, SLOT(slotAdminStatusChanged(bool))); + connect (d->mMessageClient, SIGNAL(eventClientConnected(Q_UINT32)), + this, SIGNAL(signalClientConnected(Q_UINT32))); + connect (d->mMessageClient, SIGNAL(eventClientDisconnected(Q_UINT32, bool)), + this, SIGNAL(signalClientDisconnected(Q_UINT32, bool))); + + // broacast and direct messages are treated equally on receive. + connect (d->mMessageClient, SIGNAL(forwardReceived(const QByteArray&, Q_UINT32, const QValueList<Q_UINT32>&)), + d->mMessageClient, SIGNAL(broadcastReceived(const QByteArray&, Q_UINT32))); + + } else { + // should be no problem but still has to be tested + kdDebug(11001) << k_funcinfo << "Client already exists!" << endl; + } + d->mMessageClient->setServer(d->mMessageServer); +} + +void KGameNetwork::setDiscoveryInfo(const QString& type, const QString& name) +{ + kdDebug() << k_funcinfo << type << ":" << name << endl; + d->mType = type; + d->mName = name; + tryPublish(); +} + +void KGameNetwork::tryPublish() +{ + if (d->mType.isNull() || !isOfferingConnections()) return; + if (!d->mService) d->mService = new DNSSD::PublicService(d->mName,d->mType,port()); + else { + if (d->mType!=d->mService->type()) d->mService->setType(d->mType); + if (d->mName!=d->mService->serviceName()) d->mService->setServiceName(d->mName); + } + if (!d->mService->isPublished()) d->mService->publishAsync(); +} + +void KGameNetwork::tryStopPublishing() +{ + if (d->mService) d->mService->stop(); +} + +bool KGameNetwork::offerConnections(Q_UINT16 port) +{ + kdDebug (11001) << k_funcinfo << "on port " << port << endl; + if (!isMaster()) { + setMaster(); + } + + // Make sure this is 0 + d->mDisconnectId = 0; + + // FIXME: This debug message can be removed when the program is working correct. + if (d->mMessageServer && d->mMessageServer->isOfferingConnections()) { + kdDebug (11001) << k_funcinfo << "Already running as server! Changing the port now!" << endl; + } + + tryStopPublishing(); + kdDebug (11001) << k_funcinfo << "before Server->initNetwork" << endl; + if (!d->mMessageServer->initNetwork (port)) { + kdError (11001) << k_funcinfo << "Unable to bind to port " << port << "!" << endl; + // no need to delete - we just cannot listen to the port +// delete d->mMessageServer; +// d->mMessageServer = 0; +// d->mMessageClient->setServer((KMessageServer*)0); + return false; + } + kdDebug (11001) << k_funcinfo << "after Server->initNetwork" << endl; + tryPublish(); + return true; +} + +bool KGameNetwork::connectToServer (const QString& host, Q_UINT16 port) +{ + if (host.isEmpty()) { + kdError(11001) << k_funcinfo << "No hostname given" << endl; + return false; + } + + // Make sure this is 0 + d->mDisconnectId = 0; + +// if (!d->mMessageServer) { +// // FIXME: What shall we do here? Probably must stop a running game. +// kdWarning (11001) << k_funcinfo << "We are already connected to another server!" << endl; +/// } + + if (d->mMessageServer) { + // FIXME: What shall we do here? Probably must stop a running game. + kdWarning(11001) << "we are server but we are trying to connect to another server! " + << "make sure that all clients connect to that server! " + << "quitting the local server now..." << endl; + stopServerConnection(); + d->mMessageClient->setServer((KMessageIO*)0); + delete d->mMessageServer; + d->mMessageServer = 0; + } + + kdDebug(11001) << " about to set server" << endl; + d->mMessageClient->setServer(host, port); + emit signalAdminStatusChanged(false); // as we delete the connection above isAdmin() is always false now! + + // OK: We say that we already have connected, but this isn't so yet! + // If the connection cannot be established, it will look as being disconnected + // again ("slotConnectionLost" is called). + // Shall we differ between these? + kdDebug(11001) << "connected to " << host << ":" << port << endl; + return true; +} + +Q_UINT16 KGameNetwork::port() const +{ + if (isNetwork()) { + if (isOfferingConnections()) { + return d->mMessageServer->serverPort(); + } else { + return d->mMessageClient->peerPort(); + } + } + return 0; +} + +QString KGameNetwork::hostName() const +{ + return d->mMessageClient->peerName(); +} + +bool KGameNetwork::stopServerConnection() +{ + // We still are the Master, we just don't accept further connections! + tryStopPublishing(); + if (d->mMessageServer) { + d->mMessageServer->stopNetwork(); + return true; + } + return false; +} + +bool KGameNetwork::isOfferingConnections() const +{ return (d->mMessageServer && d->mMessageServer->isOfferingConnections()); } + +void KGameNetwork::disconnect() +{ + // TODO MH + kdDebug(11001) << k_funcinfo << endl; + stopServerConnection(); + if (d->mMessageServer) { + QValueList <Q_UINT32> list=d->mMessageServer->clientIDs(); + QValueList<Q_UINT32>::Iterator it; + for( it = list.begin(); it != list.end(); ++it ) + { + kdDebug(11001) << "Client id=" << (*it) << endl; + KMessageIO *client=d->mMessageServer->findClient(*it); + if (!client) + { + continue; + } + kdDebug(11001) << " rtti=" << client->rtti() << endl; + if (client->rtti()==2) + { + kdDebug(11001) << "DIRECT IO " << endl; + } + else + { + d->mMessageServer->removeClient(client,false); + } + } + } + else + { + kdDebug(11001) << k_funcinfo << "before client->disconnect() id="<<gameId()<< endl; + //d->mMessageClient->setServer((KMessageIO*)0); + kdDebug(11001) << "+++++++++++++++++++++++++++++++++++++++++++++++++++++++"<<endl; + d->mMessageClient->disconnect(); + + kdDebug(11001) << "++++++--------------------------------------------+++++"<<endl; + } + //setMaster(); + /* + if (d->mMessageServer) { + //delete d->mMessageServer; + //d->mMessageServer=0; + server=true; + kdDebug(11001) << " server true" << endl; + d->mMessageServer->deleteClients(); + kdDebug(11001) << " server deleteClients" << endl; + } + */ + kdDebug(11001) << k_funcinfo << "DONE" << endl; +} + +void KGameNetwork::aboutToLoseConnection(Q_UINT32 clientID) +{ + kdDebug(11001) << "Storing client id of connection "<<clientID<<endl; + d->mDisconnectId = clientID; +} + +void KGameNetwork::slotResetConnection() +{ + kdDebug(11001) << "Resseting client disconnect id"<<endl; + d->mDisconnectId = 0; +} + +void KGameNetwork::electAdmin(Q_UINT32 clientID) +{ + if (!isAdmin()) { + kdWarning(11001) << k_funcinfo << "only ADMIN is allowed to call this!" << endl; + return; + } + QByteArray buffer; + QDataStream stream(buffer,IO_WriteOnly); + stream << static_cast<Q_UINT32>( KMessageServer::REQ_ADMIN_CHANGE ); + stream << clientID; + d->mMessageClient->sendServerMessage(buffer); +} + +void KGameNetwork::setMaxClients(int max) +{ + if (!isAdmin()) { + kdWarning(11001) << k_funcinfo << "only ADMIN is allowed to call this!" << endl; + return; + } + QByteArray buffer; + QDataStream stream(buffer,IO_WriteOnly); + stream << static_cast<Q_UINT32>( KMessageServer::REQ_MAX_NUM_CLIENTS ); + stream << (Q_INT32)max; + d->mMessageClient->sendServerMessage(buffer); +} + +void KGameNetwork::lock() +{ + if (messageClient()) { + messageClient()->lock(); + } +} + +void KGameNetwork::unlock() +{ + if (messageClient()) { + messageClient()->unlock(); + } +} + +// --------------------- send messages --------------------------- + +bool KGameNetwork::sendSystemMessage(int data, int msgid, Q_UINT32 receiver, Q_UINT32 sender) +{ + QByteArray buffer; + QDataStream stream(buffer,IO_WriteOnly); + stream << data; + return sendSystemMessage(buffer,msgid,receiver,sender); +} + +bool KGameNetwork::sendSystemMessage(const QString &msg, int msgid, Q_UINT32 receiver, Q_UINT32 sender) +{ + QByteArray buffer; + QDataStream stream(buffer, IO_WriteOnly); + stream << msg; + return sendSystemMessage(buffer, msgid, receiver, sender); +} + +bool KGameNetwork::sendSystemMessage(const QDataStream &msg, int msgid, Q_UINT32 receiver, Q_UINT32 sender) +{ return sendSystemMessage(((QBuffer*)msg.device())->buffer(), msgid, receiver, sender); } + +bool KGameNetwork::sendSystemMessage(const QByteArray& data, int msgid, Q_UINT32 receiver, Q_UINT32 sender) +{ + QByteArray buffer; + QDataStream stream(buffer,IO_WriteOnly); + if (!sender) { + sender = gameId(); + } + + Q_UINT32 receiverClient = KGameMessage::rawGameId(receiver); // KGame::gameId() + int receiverPlayer = KGameMessage::rawPlayerId(receiver); // KPlayer::id() + + KGameMessage::createHeader(stream, sender, receiver, msgid); + stream.writeRawBytes(data.data(), data.size()); + + /* + kdDebug(11001) << "transmitGameClientMessage msgid=" << msgid << " recv=" + << receiver << " sender=" << sender << " Buffersize=" + << buffer.size() << endl; + */ + + if (!d->mMessageClient) { + // No client created, this should never happen! + // Having a local game means we have our own + // KMessageServer and we are the only client. + kdWarning (11001) << k_funcinfo << "We don't have a client! Should never happen!" << endl; + return false; + } + + if (receiverClient == 0 || receiverPlayer != 0) + { + // if receiverClient == 0 this is a broadcast message. if it is != 0 but + // receiverPlayer is also != 0 we have to send broadcast anyway, because the + // KPlayer object on all clients needs to receive the message. + d->mMessageClient->sendBroadcast(buffer); + } + else + { + d->mMessageClient->sendForward(buffer, receiverClient); + } + return true; +} + +bool KGameNetwork::sendMessage(int data, int msgid, Q_UINT32 receiver, Q_UINT32 sender) +{ return sendSystemMessage(data,msgid+KGameMessage::IdUser,receiver,sender); } + +bool KGameNetwork::sendMessage(const QString &msg, int msgid, Q_UINT32 receiver, Q_UINT32 sender) +{ return sendSystemMessage(msg,msgid+KGameMessage::IdUser,receiver,sender); } + +bool KGameNetwork::sendMessage(const QDataStream &msg, int msgid, Q_UINT32 receiver, Q_UINT32 sender) +{ return sendSystemMessage(msg, msgid+KGameMessage::IdUser, receiver, sender); } + +bool KGameNetwork::sendMessage(const QByteArray &msg, int msgid, Q_UINT32 receiver, Q_UINT32 sender) +{ return sendSystemMessage(msg, msgid+KGameMessage::IdUser, receiver, sender); } + +void KGameNetwork::sendError(int error,const QByteArray& message, Q_UINT32 receiver, Q_UINT32 sender) +{ + QByteArray buffer; + QDataStream stream(buffer,IO_WriteOnly); + stream << (Q_INT32) error; + stream.writeRawBytes(message.data(), message.size()); + sendSystemMessage(stream,KGameMessage::IdError,receiver,sender); +} + + +// ----------------- receive messages from the network +void KGameNetwork::receiveNetworkTransmission(const QByteArray& receiveBuffer, Q_UINT32 clientID) +{ + QDataStream stream(receiveBuffer, IO_ReadOnly); + int msgid; + Q_UINT32 sender; // the id of the KGame/KPlayer who sent the message + Q_UINT32 receiver; // the id of the KGame/KPlayer the message is for + KGameMessage::extractHeader(stream, sender, receiver, msgid); +// kdDebug(11001) << k_funcinfo << "id=" << msgid << " sender=" << sender << " recv=" << receiver << endl; + + // No broadcast : receiver==0 + // No player isPlayer(receiver) + // Different game gameId()!=receiver + if (receiver && receiver!=gameId() && !KGameMessage::isPlayer(receiver) ) + { + // receiver=0 is broadcast or player message + kdDebug(11001) << k_funcinfo << "Message not meant for us " + << gameId() << "!=" << receiver << " rawid=" + << KGameMessage::rawGameId(receiver) << endl; + return; + } + else if (msgid==KGameMessage::IdError) + { + QString text; + Q_INT32 error; + stream >> error; + kdDebug(11001) << k_funcinfo << "Got IdError " << error << endl; + text = KGameError::errorText(error, stream); + kdDebug(11001) << "Error text: " << text.latin1() << endl; + emit signalNetworkErrorMessage((int)error,text); + } + else + { + networkTransmission(stream, msgid, receiver, sender, clientID); + } +} + +// -------------- slots for the signals of the client +void KGameNetwork::slotAdminStatusChanged(bool isAdmin) +{ + emit signalAdminStatusChanged(isAdmin); + +// TODO: I'm pretty sure there are a lot of things that should be done here... +} + +void KGameNetwork::Debug() +{ + kdDebug(11001) << "------------------- KNETWORKGAME -------------------------" << endl; + kdDebug(11001) << "gameId " << gameId() << endl; + kdDebug(11001) << "gameMaster " << isMaster() << endl; + kdDebug(11001) << "gameAdmin " << isAdmin() << endl; + kdDebug(11001) << "---------------------------------------------------" << endl; +} + +/* + * vim: et sw=2 + */ diff --git a/libkdegames/kgame/kgamenetwork.h b/libkdegames/kgame/kgamenetwork.h new file mode 100644 index 00000000..6ff5cf94 --- /dev/null +++ b/libkdegames/kgame/kgamenetwork.h @@ -0,0 +1,431 @@ +/* + 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$ +*/ +#ifndef __KGAMENETWORK_H_ +#define __KGAMENETWORK_H_ + +#include <qstring.h> +#include <qobject.h> +#include <kdemacros.h> +class KGameIO; +class KMessageClient; +class KMessageServer; + +class KGameNetworkPrivate; + +/** + * The KGameNetwork class is the KGame class with network + * support. All other features are the same but they are + * now network transparent. It is not used directly but + * only via a KGame object. So you do not really have + * to bother with this object. + * + * @short The main KDE game object + * @author Martin Heni <[email protected]> + * @version $Id$ + */ +class KDE_EXPORT KGameNetwork : public QObject +{ + Q_OBJECT + +public: + /** + * Create a KGameNetwork object + */ + KGameNetwork(int cookie=42,QObject* parent=0); + virtual ~KGameNetwork(); + + /** + * Gives debug output of the game status + **/ + virtual void Debug(); + + /** + * @return TRUE if this is a network game - i.e. you are either MASTER or + * connected to a remote MASTER. + **/ + bool isNetwork() const; + + /** + * Is this the game MASTER (i.e. has started theKMessageServer). A + * game has always exactly one MASTER. This is either a KGame object (i.e. a + * Client) or an own MessageServer-process. A KGame object that has the + * MASTER status is always admin. + * + * You probably don't want to use this. It is a mostly internal method which + * will probably become protected. Better use isAdmin + * + * @see isAdmin + * @return Whether this client has started the KMessageServer + **/ + bool isMaster() const; + + /** + * The admin of a game is the one who initializes newly connected clients + * using negotiateNetworkGame and is allowed to configure the game. + * E.g. only the admin is allowed to use KGame::setMaxPlayers. + * + * If one KGame object in the game is MASTER then this client is the admin + * as well. isMaster and isAdmin differ only if the KMessageServer + * is running in an own process. + * @return Whether this client (KGame object) is the admin + **/ + bool isAdmin() const; + + /** + * The unique ID of this game + * + * @return int id + **/ + Q_UINT32 gameId() const; + + /** + * Inits a network game as network MASTER. Note that if the + * KMessageServer is not yet started it will be started here (see + * setMaster). Any existing connection will be disconnected. + * + * If you already offer connections the port is changed. + * + * @param port The port on which the service is offered + * @return true if it worked + **/ + bool offerConnections (Q_UINT16 port); + + /** + * Announces game MASTER on network using DNS-SD. Clients then can discover it using + * DNSSD::ServiceBrowser (or KGameConnectWidget) instead of manually entering + * IP address. + * @param type service type (something like _kwin4._tcp). + * It should be unique for application. + * @param name game name that will be displayed by clients. If not + * set hostname will be used. In case of name conflict -2, -3 and so on will be added to name. + * @since 3.4 + **/ + void setDiscoveryInfo(const QString& type, const QString& name=QString::null); + + /** + * Inits a network game as a network CLIENT + * + * @param host the host to which we want to connect + * @param port the port we want to connect to + * + * @return true if connected + **/ + bool connectToServer(const QString& host, Q_UINT16 port); + + /** + * @since 3.2 + * @return The port we are listening to if offerConnections was called + * or the port we are connected to if connectToServer was called. + * Otherwise 0. + **/ + Q_UINT16 port() const; + + /** + * @since 3.2 + * @return The name of the host that we are currently connected to is + * isNetwork is TRUE and we are not the MASTER, i.e. if connectToServer + * was called. Otherwise this will return "localhost". + **/ + QString hostName() const; + + /** + * Stops offering server connections - only for game MASTER + * @return true + **/ + bool stopServerConnection(); + + /** + * Changes the maximal connection number of the KMessageServer to max. + * -1 Means infinite connections are possible. Note that existing + * connections are not affected, so even if you set this to 0 in a running + * game no client is being disconnected. You can call this only if you are + * the ADMIN! + * + * @see KMessageServer::setMaxClients + * @param max The maximal number of connections possible. + **/ + void setMaxClients(int max); + + //AB: is this now internal only? Can we make it protected (maybe with + //friends)? sendSystemMessage AND sendMessage is very confusing to the + //user. + /** + * Sends a network message msg with a given msg id msgid to all clients. + * Use this to communicate with KGame (e.g. to add a player ot to configure + * the game - usually not necessary). + * + * For your own messages use sendMessage instead! This is mostly + * internal! + * + * @param buffer the message which will be send. See messages.txt for contents + * @param msgid an id for this message. See + * KGameMessage::GameMessageIds + * @param receiver the KGame / KPlayer this message is for. + * @param sender The KGame / KPlayer this message is from (i.e. + * you). You + * probably want to leave this 0, then KGameNetwork will create the correct + * value for you. You might want to use this if you send a message from a + * specific player. + * @return true if worked + */ + // AB: TODO: doc on how "receiver" and "sender" should be created! + bool sendSystemMessage(const QByteArray& buffer, int msgid, Q_UINT32 receiver=0, Q_UINT32 sender=0); + + /** + * @overload + **/ + bool sendSystemMessage(int data, int msgid, Q_UINT32 receiver=0, Q_UINT32 sender=0); + + /** + * @overload + **/ + bool sendSystemMessage(const QDataStream &msg, int msgid, Q_UINT32 receiver=0, Q_UINT32 sender=0); + + /** + * @overload + **/ + bool sendSystemMessage(const QString& msg, int msgid, Q_UINT32 receiver=0, Q_UINT32 sender=0); + + /** + * Sends a network message + * @param error The error code + * @param message The error message - use KGameError + * @param receiver the KGame / KPlayer this message is for. 0 For + * all + * @param sender The KGame / KPlayer this message is from (i.e. + * you). You probably want to leave this 0, then KGameNetwork will create + * the correct value for you. You might want to use this if you send a + * message from a specific player. + **/ + void sendError(int error, const QByteArray& message, Q_UINT32 receiver=0, Q_UINT32 sender=0); + + /** + * Are we still offer offering server connections - only for game MASTER + * @return true/false + **/ + bool isOfferingConnections() const; + + /** + * Application cookie. this idendifies the game application. It + * help to distinguish between e.g. KPoker and KWin4 + * @return the application cookie + **/ + int cookie() const; + + /** + * Send a network message msg with a given message ID msgid to all clients. + * You want to use this to send a message to the clients. + * + * Note that a message is always sent to ALL clients! This is necessary so + * that all clients always have the same data and can easily be changed from + * network to non-network without restarting the game. If you want a + * specific KGame / KPlayer to react to the message use the + * receiver and sender parameters. See KGameMessage::calsMessageId + * + * SendMessage differs from sendSystemMessage only by the msgid parameter. + * sendSystemMessage is thought as a KGame only mehtod while + * sendMessage is for public use. The msgid parameter will be + * +=KGameMessage::IdUser and in KGame::signalNetworkData msgid will + * be -= KGameMessage::IdUser again, so that one can easily distinguish + * between system and user messages. + * + * Use sendSystemMessage to comunicate with KGame (e.g. by adding a + * player) and sendMessage for your own user message. + * + * Note: a player should send messages through a KGameIO! + * + * @param buffer the message which will be send. See messages.txt for contents + * @param msgid an id for this message. See KGameMessage::GameMessageIds + * @param receiver the KGame / KPlayer this message is for. + * @param sender The KGame / KPlayer this message is from (i.e. + * you). You + * probably want to leave this 0, then KGameNetwork will create the correct + * value for you. You might want to use this if you send a message from a + * specific player. + * @return true if worked + **/ + // AB: TODO: doc on how "receiver" and "sender" should be created! + bool sendMessage(const QByteArray& buffer, int msgid, Q_UINT32 receiver=0, Q_UINT32 sender=0); + + /** + * This is an overloaded member function, provided for convenience. + **/ + bool sendMessage(const QDataStream &msg, int msgid, Q_UINT32 receiver=0, Q_UINT32 sender=0); + + /** + * This is an overloaded member function, provided for convenience. + **/ + bool sendMessage(const QString& msg, int msgid, Q_UINT32 receiver=0, Q_UINT32 sender=0); + + /** + * This is an overloaded member function, provided for convenience. + **/ + bool sendMessage(int data, int msgid, Q_UINT32 receiver=0, Q_UINT32 sender=0); + + + /** + * Called by ReceiveNetworkTransmission(). Will be overwritten by + * KGame and handle the incoming message. + **/ + virtual void networkTransmission(QDataStream&, int, Q_UINT32, Q_UINT32, Q_UINT32 clientID) = 0; + + + /** + * Disconnect the current connection and establish a new local one. + **/ + void disconnect(); + + + /** + * If you are the ADMIN of the game you can give the ADMIN status away to + * another client. Use this e.g. if you want to quit the game or if you want + * another client to administrate the game (note that disconnect calls + * this automatically). + * @param clientID the ID of the new ADMIN (note: this is the _client_ID + * which has nothing to do with the player IDs. See KMessageServer) + **/ + void electAdmin(Q_UINT32 clientID); + + /** + * Don't use this unless you really know what youre doing! You might + * experience some strange behaviour if you send your messages directly + * through the KMessageClient! + * + * @return a pointer to the KMessageClient used internally to send the + * messages. You should rather use one of the send functions! + **/ + KMessageClient* messageClient() const; + + /** + * Don't use this unless you really know what you are doing! You might + * experience some strange behaviour if you use the message server directly! + * + * @return a pointer to the message server if this is the MASTER KGame + * object. Note that it might be possible that no KGame object contains + * the KMessageServer at all! It might even run stand alone! + **/ + KMessageServer* messageServer() const; + + /** + * You should call this before doing thigs like, e.g. qApp->processEvents(). + * Don't forget to call unlock once you are done! + * + * @see KMessageClient::lock + **/ + virtual void lock(); + + /** + * @see KMessageClient::unlock + **/ + virtual void unlock(); + +signals: + /** + * A network error occurred + * @param error the error code + * @param text the error text + */ + void signalNetworkErrorMessage(int error, QString text); + + /** + * Our connection to the KMessageServer has broken. + * See KMessageClient::connectionBroken + **/ + void signalConnectionBroken(); + + /** + * This signal is emitted whenever the KMessageServer sends us a message that a + * new client connected. KGame uses this to call KGame::negotiateNetworkGame + * for the newly connected client if we are admin (see isAdmin) + * + * @see KMessageClient::eventClientConnected + * + * @param clientID the ID of the newly connected client + **/ + void signalClientConnected(Q_UINT32 clientID); + + /** + * This signal is emitted whenever the KMessageServer sends us a message + * that a connection to a client was detached. The second parameter can be used + * to distinguish between network errors or removing on purpose. + * + * @see KMessageClient::eventClientDisconnected + * + * @param clientID the client that has disconnected + * @param broken true if the connection was lost because of a network error, false + * if the connection was closed by the message server admin. + */ + void signalClientDisconnected(Q_UINT32 clientID, bool broken); + + /** + * This client gets or loses the admin status. + * @see KMessageClient::adminStatusChanged + * @param isAdmin True if this client gets the ADMIN status otherwise FALSE + **/ + void signalAdminStatusChanged(bool isAdmin); + +protected: + /** + * @internal + * Start a KMessageServer object and use it as the MASTER of the game. + * Note that you must not call this if there is already another master + * running! + **/ + void setMaster(); + +protected slots: + /** + * Called by KMessageClient::broadcastReceived() and will check if the + * message format is valid. If it is not, it will generate an error (see + * signalNetworkVersionError and signalNetworkErorrMessage). + * If it is valid, the pure virtual method networkTransmission() is called. + * (This one is overwritten in KGame.) + **/ + void receiveNetworkTransmission(const QByteArray& a, Q_UINT32 clientID); + + /** + * This KGame object receives or loses the admin status. + * @param isAdmin Whether we are admin or not + **/ + void slotAdminStatusChanged(bool isAdmin); + + /** + * Called when the network connection is about to terminate. Is used + * to store the network parameter like the game id + */ + void aboutToLoseConnection(Q_UINT32 id); + + /** + * Called when the network connection is terminated. Used to clean + * up the disconnect parameter + */ + void slotResetConnection(); + + +private: + void tryPublish(); + void tryStopPublishing(); + KGameNetworkPrivate* d; +}; + +#endif diff --git a/libkdegames/kgame/kgameprocess.cpp b/libkdegames/kgame/kgameprocess.cpp new file mode 100644 index 00000000..96efe0ce --- /dev/null +++ b/libkdegames/kgame/kgameprocess.cpp @@ -0,0 +1,158 @@ +/* + 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 "kgameprocess.h" +#include "kplayer.h" +#include "kgame.h" +#include "kgamemessage.h" +#include "kmessageio.h" + +#include <krandomsequence.h> + +#include <qbuffer.h> +#include <qdatastream.h> +#include <qcstring.h> + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +#define READ_BUFFER_SIZE 1024 + +// ----------------------- Process Child --------------------------- + +KGameProcess::KGameProcess() : QObject(0,0) +{ + mTerminate=false; + // Check whether a player is set. If not create one! + rFile.open(IO_ReadOnly|IO_Raw,stdin); + wFile.open(IO_WriteOnly|IO_Raw,stdout); + mMessageIO=new KMessageFilePipe(this,&rFile,&wFile); +// mMessageClient=new KMessageClient(this); +// mMessageClient->setServer(mMessageIO); +// connect (mMessageClient, SIGNAL(broadcastReceived(const QByteArray&, Q_UINT32)), +// this, SLOT(receivedMessage(const QByteArray&, Q_UINT32))); + connect (mMessageIO, SIGNAL(received(const QByteArray&)), + this, SLOT(receivedMessage(const QByteArray&))); + fprintf(stderr,"KGameProcess::constructor %p %p\n",&rFile,&wFile); + + mRandom = new KRandomSequence; + mRandom->setSeed(0); +} +KGameProcess::~KGameProcess() +{ + delete mRandom; + //delete mMessageClient; + //delete mMessageServer; + delete mMessageIO; + rFile.close(); + wFile.close(); + fprintf(stderr,"KGameProcess::destructor\n"); +} + + +bool KGameProcess::exec(int argc, char *argv[]) +{ + // Get id and cookie, ... from command line + processArgs(argc,argv); + do + { + mMessageIO->exec(); + } while(!mTerminate); + return true; +} + +// You have to do this to create a message +// QByteArray buffer; +// QDataStream wstream(buffer,IO_WriteOnly); +// then stream data into the stream and call this function +void KGameProcess::sendSystemMessage(QDataStream &stream,int msgid,Q_UINT32 receiver) +{ + fprintf(stderr,"KGameProcess::sendMessage id=%d recv=%d",msgid,receiver); + QByteArray a; + QDataStream outstream(a,IO_WriteOnly); + + QBuffer *device=(QBuffer *)stream.device(); + QByteArray data=device->buffer();; + + KGameMessage::createHeader(outstream,0,receiver,msgid); + outstream.writeRawBytes(data.data(),data.size()); + + //if (mMessageClient) mMessageClient->sendBroadcast(a); + // TODO: The fixed received 2 will cause problems. But how to address the + // proper one? +// if (mMessageClient) mMessageClient->sendForward(a,2); + if (mMessageIO) mMessageIO->send(a); +} + +void KGameProcess::sendMessage(QDataStream &stream,int msgid,Q_UINT32 receiver) +{ + sendSystemMessage(stream,msgid+KGameMessage::IdUser,receiver); +} + +void KGameProcess::processArgs(int argc, char *argv[]) +{ + int v=0; + if (argc>2) + { + v=atoi(argv[2]); + //kdDebug(11001) << "cookie (unused) " << v << endl; + } + if (argc>1) + { + v=atoi(argv[1]); + //kdDebug(11001) << "id (unused) " << v << endl; + } + fprintf(stderr,"processArgs \n"); + fflush(stderr); +} + +void KGameProcess::receivedMessage(const QByteArray& receiveBuffer) +{ + QDataStream stream(receiveBuffer, IO_ReadOnly); + int msgid; + Q_UINT32 sender; + Q_UINT32 receiver; + KGameMessage::extractHeader(stream, sender, receiver, msgid); + fprintf(stderr,"------ receiveNetworkTransmission(): id=%d sender=%d,recv=%d\n",msgid,sender,receiver); + switch(msgid) + { + case KGameMessage::IdTurn: + Q_INT8 b; + stream >> b; + emit signalTurn(stream,(bool)b); + break; + case KGameMessage::IdIOAdded: + Q_INT16 id; + stream >> id; + emit signalInit(stream,(int)id); + break; + default: + emit signalCommand(stream,msgid-KGameMessage::IdUser,receiver,sender); + break; + } +} + +#include "kgameprocess.moc" diff --git a/libkdegames/kgame/kgameprocess.h b/libkdegames/kgame/kgameprocess.h new file mode 100644 index 00000000..a8db4fcd --- /dev/null +++ b/libkdegames/kgame/kgameprocess.h @@ -0,0 +1,242 @@ +/* + 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$ +*/ +#ifndef __KGAMEPROCESS_H_ +#define __KGAMEPROCESS_H_ + +#include <qstring.h> +#include <qobject.h> +#include <qfile.h> + +#include "kgameproperty.h" +#include <krandomsequence.h> +#include <kdemacros.h> +class KPlayer; +class KMessageFilePipe; + +/** + * This is the process class used on the computer player + * side to communicate with its counterpart KProcessIO class. + * Using these two classes will give fully transparent communication + * via QDataStreams. + */ +class KDE_EXPORT KGameProcess: public QObject +{ + Q_OBJECT + + public: + /** + * Creates a KGameProcess class. Done only in the computer + * player. To activate the communication you have to call + * the exec function of this class which will listen + * to the communication and emit signals to notify you of + * any incoming messages. + * Note: This function will only return after you set + * setTerminate(true) in one of the received signals. + * So you can not do any computer calculation after the exec function. + * Instead you react on the signals which are emitted after a + * message is received and perform the calculations there! + * Example: + * \code + * int main(int argc ,char * argv[]) + * { + * KGameProcess proc; + * connect(&proc,SIGNAL(signalCommand(QDataStream &,int ,int ,int )), + * this,SLOT(slotCommand(QDataStream & ,int ,int ,int ))); + * connect(&proc,SIGNAL(signalInit(QDataStream &,int)), + * this,SLOT(slotInit(QDataStream & ,int ))); + * connect(&proc,SIGNAL(signalTurn(QDataStream &,bool )), + * this,SLOT(slotTurn(QDataStream & ,bool ))); + * return proc.exec(argc,argv); + * } + * \endcode + */ + KGameProcess(); + /** + * Destruct the process + */ + ~KGameProcess(); + + /** + * Enters the event loop of the computer process. Does only + * return on setTerminate(true)! + */ + bool exec(int argc, char *argv[]); + + /** + * Should the computer process leave its exec function? + * Activated if you setTerminate(true); + * + * @return true/false + */ + bool terminate() const {return mTerminate;} + + /** + * Set this to true if the computer process should end, ie + * leave its exec function. + * + * @param b true for exit the exec function + */ + void setTerminate(bool b) {mTerminate=b;} + + /** + * Sends a message to the corresponding KGameIO + * device. Works like the sendSystemMessage but + * for user id's + * + * @param stream the QDataStream containing the message + * @param msgid the message id for the message + * @param receiver unused + */ + void sendMessage(QDataStream &stream,int msgid,Q_UINT32 receiver=0); + + /** + * Sends a system message to the corresonding KGameIO device. + * This will normally be either a performed move or a query + * (IdProcessQuery). The query option is a way to communicate + * with the KGameIO at the other side and e.g. retrieve some + * game relevant data from here. + * Exmaple for a query: + * \code + * QByteArray buffer; + * QDataStream out(buffer,IO_WriteOnly); + * int msgid=KGameMessage::IdProcessQuery; + * out << (int)1; + * proc.sendSystemMessage(out,msgid,0); + * \endcode + * + * @param stream the QDataStream containing the message + * @param msgid the message id for the message + * @param receiver unused + */ + void sendSystemMessage(QDataStream &stream,int msgid,Q_UINT32 receiver=0); + + /** + * Returns a pointer to a KRandomSequence. You can generate + * random numbers via e.g. + * \code + * random()->getLong(100); + * \endcode + * + * @return KRandomSequence pointer + */ + KRandomSequence *random() {return mRandom;} + + protected: + /** + * processes the command line argumens to set up the computer player + * Pass the argumens exactely as given by main() + */ + void processArgs(int argc, char *argv[]); + + protected slots: + /** + * A message is received via the interprocess connection. The + * appropriate signals are called. + */ + void receivedMessage(const QByteArray& receiveBuffer); + + signals: + /** + * The generic communication signal. You have to connect to this + * signal to generate a valid computer response onto arbitrary messages. + * All signals but IdIOAdded and IdTurn end up here! + * Example: + * \code + * void Computer::slotCommand(int &msgid,QDataStream &in,QDataStream &out) + * { + * Q_INT32 data,move; + * in >> data; + * // compute move ... + * move=data*2; + * out << move; + * } + * \endcode + * + * @param inputStream the incoming data stream + * @param msgid the message id of the message which got transmitted to the computer + * @param receiver the id of the receiver + * @param sender the id of the sender + */ + void signalCommand(QDataStream &inputStream,int msgid,int receiver,int sender); + + /** + * This signal is emmited if the computer player should perform a turn. + * Calculations can be made here and the move can then be send back with + * sendSystemMessage with the message id KGameMessage::IdPlayerInput. + * These must provide a move which complies to your other move syntax as + * e.g. produces by keyboard or mouse input. + * Additonal data which have been written into the stream from the + * ProcessIO's signal signalPrepareTurn can be retrieved from the + * stream here. + * Example: + * \code + * void slotTurn(QDataStream &in,bool turn) + * { + * int id; + * int recv; + * QByteArray buffer; + * QDataStream out(buffer,IO_WriteOnly); + * if (turn) + * { + * // Create a move - the format is yours to decide + * // It arrives exactly as this in the kgame inputMove function!! + * Q_INT8 x1,y1,pl; + * pl=-1; + * x1=proc.random()->getLong(8); + * y1=proc.random()->getLong(8); + * // Stream it + * out << pl << x1 << y1; + * id=KGameMessage::IdPlayerInput; + * proc.sendSystemMessage(out,id,0); + * } + * } + * \endcode + * + * @param stream The datastream which contains user data + * @param turn True or false whether the turn is activated or deactivated + * + */ + void signalTurn(QDataStream &stream,bool turn); + + /** + * This signal is emmited when the process is initialized, i.e. added + * to a KPlayer. Initial initialisation can be performed here be reacting + * to the KProcessIO signal signalIOAdded and retrieving the data here + * from the stream. + * It works just as the signalTurn() but is only send when the player is + * added to the game, i.e. it needs some initialization data + * + * @param stream The datastream which contains user data + * @param userid The userId of the player. (Careful to rely on it yet) + */ + void signalInit(QDataStream &stream,int userid); + + protected: + bool mTerminate; + KMessageFilePipe *mMessageIO; + private: + QFile rFile; + QFile wFile; + KRandomSequence* mRandom; +}; +#endif diff --git a/libkdegames/kgame/kgameproperty.cpp b/libkdegames/kgame/kgameproperty.cpp new file mode 100644 index 00000000..68a33bcb --- /dev/null +++ b/libkdegames/kgame/kgameproperty.cpp @@ -0,0 +1,211 @@ +/* + This file is part of the KDE games library + Copyright (C) 2001 Andreas Beckermann ([email protected]) + Copyright (C) 2001 Martin Heni ([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 "kgameproperty.h" +#include "kgamepropertyhandler.h" +#include "kgamemessage.h" +#include "kplayer.h" +#include "kgame.h" + +#define KPLAYERHANDLER_LOAD_COOKIE 6239 + +KGamePropertyBase::KGamePropertyBase(int id, KGame* parent) +{ + init(); + registerData(id, parent); +} + +KGamePropertyBase::KGamePropertyBase(int id, KPlayer* parent) +{ + init(); + registerData(id, parent); +} + +KGamePropertyBase::KGamePropertyBase(int id, KGamePropertyHandler* owner) +{ + init(); + registerData(id, owner); +} + +KGamePropertyBase::KGamePropertyBase() +{ + init(); +} + +KGamePropertyBase::~KGamePropertyBase() +{ + unregisterData(); +} + +void KGamePropertyBase::init() +{ + mOwner = 0; + setDirty(false); + + // this is very useful and used by e.g. KGameDialog so + // it is activated by default. Big games may profit by deactivating it to get + // a better performance. + setEmittingSignal(true); + + setOptimized(false); + + //setReadOnly(false); + mFlags.bits.locked = false ; // setLocked(false); is NOT possible as it checks whether isLocked() allows to change the status + + // local is default + setPolicy(PolicyLocal); +} + +int KGamePropertyBase::registerData(int id, KGame* owner, QString name) +{ return registerData(id, owner->dataHandler(), name); } + +int KGamePropertyBase::registerData(int id, KPlayer* owner, QString name) +{ return registerData(id, owner->dataHandler(), name); } + +int KGamePropertyBase::registerData( KGamePropertyHandler* owner,PropertyPolicy p, QString name) +{ return registerData(-1, owner,p, name); } + +int KGamePropertyBase::registerData(int id, KGamePropertyHandler* owner, QString name) +{ return registerData(id, owner,PolicyUndefined, name); } + +int KGamePropertyBase::registerData(int id, KGamePropertyHandler* owner,PropertyPolicy p, QString name) +{ +// we don't support changing the id + if (!owner) { + kdWarning(11001) << k_funcinfo << "Resetting owner=0. Sure you want to do this?" << endl; + mOwner=0; + return -1; + } + if (!mOwner) { + if (id==-1) { + id=owner->uniquePropertyId(); + } + mId = id; + mOwner = owner; + mOwner->addProperty(this, name); + if (p!=PolicyUndefined) { + setPolicy(p); + } else { + setPolicy(mOwner->policy()); + } + } + return mId; +} + +void KGamePropertyBase::unregisterData() +{ + if (!mOwner) { + return; + } + mOwner->removeProperty(this); + mOwner = 0; +} + +bool KGamePropertyBase::sendProperty() +{ + QByteArray b; + QDataStream s(b, IO_WriteOnly); + KGameMessage::createPropertyHeader(s, id()); + save(s); + if (mOwner) { + return mOwner->sendProperty(s); + } else { + kdError(11001) << k_funcinfo << "Cannot send because there is no receiver defined" << endl; + return false; + } +} + +bool KGamePropertyBase::sendProperty(const QByteArray& data) +{ + QByteArray b; + QDataStream s(b, IO_WriteOnly); + KGameMessage::createPropertyHeader(s, id()); + s.writeRawBytes(data.data(), data.size()); + if (mOwner) { + return mOwner->sendProperty(s); + } else { + kdError(11001) << k_funcinfo << ": Cannot send because there is no receiver defined" << endl; + return false; + } +} + +bool KGamePropertyBase::lock() +{ + if (isLocked()) { + return false; + } + setLock(true); + return true; +} + +bool KGamePropertyBase::unlock(bool force) +{ + if (isLocked() && !force) { + return false; + } + setLock(false); + return true; +} + +void KGamePropertyBase::setLock(bool l) +{ + QByteArray b; + QDataStream s(b, IO_WriteOnly); + KGameMessage::createPropertyCommand(s, IdCommand, id(), CmdLock); + + s << (Q_INT8)l; + if (mOwner) { + mOwner->sendProperty(s); + } else { + kdError(11001) << k_funcinfo << ": Cannot send because there is no receiver defined" << endl; + return ; + } +} + +void KGamePropertyBase::emitSignal() +{ + //kdDebug(11001) << k_funcinfo << ": mOwnerP="<< mOwner << " id=" << id() << endl; + if (mOwner ) { + mOwner->emitSignal(this); + } else { + kdError(11001) << k_funcinfo << ":id="<<id()<<" Cannot emitSignal because there is no handler set" << endl; + } +} + +void KGamePropertyBase::command(QDataStream& s, int cmd, bool isSender) +{ + switch (cmd) { + case CmdLock: + { + if (!isSender) { + Q_INT8 locked; + s >> locked; + mFlags.bits.locked = (bool)locked ; + break; + } + } + default: // probably in derived classes + break; + } +} + diff --git a/libkdegames/kgame/kgameproperty.h b/libkdegames/kgame/kgameproperty.h new file mode 100644 index 00000000..c6915606 --- /dev/null +++ b/libkdegames/kgame/kgameproperty.h @@ -0,0 +1,848 @@ +/* + This file is part of the KDE games library + Copyright (C) 2001 Andreas Beckermann ([email protected]) + Copyright (C) 2001 Martin Heni ([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. +*/ + +#ifndef __KGAMEPROPERTY_H_ +#define __KGAMEPROPERTY_H_ + +#include <qdatastream.h> + +#include <kdebug.h> +#include <typeinfo> +#include <kdemacros.h> +class KGame; +class KPlayer; +class KGamePropertyHandler; +using namespace std; + +/** + * @short Base class of KGameProperty + * + * The KGamePropertyBase class is the base class of KGameProperty. See + * KGameProperty for further information. + * + * @author Andreas Beckermann <[email protected]> + **/ +class KDE_EXPORT KGamePropertyBase +{ +public: + enum PropertyDataIds { // these belong to KPlayer/KGame! + //KPlayer + IdGroup=1, + IdUserId=2, + IdAsyncInput=3, + IdTurn=4, + IdName=5, + + //KGame + IdGameStatus=6, + IdMaxPlayer=7, + IdMinPlayer=8, + + // Input Grabbing + IdGrabInput=16, + IdReleaseInput=17, + + IdCommand, // Reserved for internal use + IdUser=256, + + IdAutomatic=0x7000 // Id's from here on are automatically given (16bit) + }; + + /** + * Commands for advanced properties (Q_INT8) + **/ + enum PropertyCommandIds + { + // General + CmdLock=1, + + // Array + CmdAt=51, + CmdResize=52, + CmdFill=53, + CmdSort=54, + // List (could be the same id's actually) + CmdInsert=61, + CmdAppend=62, + CmdRemove=63, + CmdClear=64 + }; + + /** + * The policy of the property. This can be PolicyClean (setValue uses + * send), PolicyDirty (setValue uses changeValue) or + * PolicyLocal (setValue uses setLocal). + * + * A "clean" policy means that the property is always the same on every + * client. This is achieved by calling send which actually changes + * the value only when the message from the MessageServer is received. + * + * A "dirty" policy means that as soon as setValue is called the + * property is changed immediately. And additionally sent over network. + * This can sometimes lead to bugs as the other clients do not + * immediately have the same value. For more information see + * changeValue. + * + * PolicyLocal means that a KGameProperty behaves like ever + * "normal" variable. Whenever setValue is called (e.g. using "=") + * the value of the property is changes immediately without sending it + * over network. You might want to use this if you are sure that all + * clients set the property at the same time. + **/ + enum PropertyPolicy + { + PolicyUndefined = 0, + PolicyClean = 1, + PolicyDirty = 2, + PolicyLocal = 3 + }; + + + /** + * Constructs a KGamePropertyBase object and calls registerData. + * @param id The id of this property. MUST be UNIQUE! Used to send and + * receive changes in the property of the playere automatically via + * network. + * @param owner The owner of the object. Must be a KGamePropertyHandler which manages + * the changes made to this object, i.e. which will send the new data + **/ + KGamePropertyBase(int id, KGamePropertyHandler* owner); + + KGamePropertyBase(int id, KGame* parent); + KGamePropertyBase(int id, KPlayer* parent); + + /** + * Creates a KGamePropertyBase object without an owner. Remember to call + * registerData! + **/ + KGamePropertyBase(); + + virtual ~KGamePropertyBase(); + + /** + * Changes the consistency policy of a property. The + * PropertyPolicy is one of PolicyClean (defaulz), PolicyDirty or PolicyLocal. + * + * It is up to you to decide how you want to work. + **/ + void setPolicy(PropertyPolicy p) { mFlags.bits.policy = p; } + + /** + * @return The default policy of the property + **/ + PropertyPolicy policy() const { return (PropertyPolicy)mFlags.bits.policy; } + + /** + * Sets this property to emit a signal on value changed. + * As the proerties do not inehrit QObject for optimisation + * this signal is emited via the KPlayer or KGame object + **/ + void setEmittingSignal(bool p) { mFlags.bits.emitsignal=p; } + + /** + * See also setEmittingSignal + * @return Whether this property emits a signal on value change + **/ + bool isEmittingSignal() const { return mFlags.bits.emitsignal; } + + /** + * Sets this property to try to optimize signal and network handling + * by not sending it out when the property value is not changed. + **/ + void setOptimized(bool p) { mFlags.bits.optimize = p ; } + + /** + * See also setOptimize + * @return Whether the property optimizes access (signals,network traffic) + **/ + bool isOptimized() const { return mFlags.bits.optimize; } + + /** + * @return Whether this property is "dirty". See also setDirty + **/ + bool isDirty() const { return mFlags.bits.dirty; } + + /** + * A locked property can only be changed by the player who has set the + * lock. See also setLocked + * @return Whether this property is currently locked. + **/ + bool isLocked() const { return mFlags.bits.locked; } + + /** + * A locked property can only be changed by the player who has set the + * lock. + * + * You can only call this if isLocked is false. A message is sent + * over network so that the property is locked for all players except + * you. + * + * @return returns false if the property can not be locked, i.e. it is already locked + * + **/ + bool lock(); + + /** + * A locked property can only be changed by the player who has set the + * lock. + * + * You can only call this if isLocked is false. A message is sent + * over network so that the property is locked for all players except + * you. + * + * @return returns false if the property can not be locked, i.e. it is already locked + * + **/ + bool unlock(bool force=false); + + /** + * This will read the value of this property from the stream. You MUST + * overwrite this method in order to use this class + * @param s The stream to read from + **/ + virtual void load(QDataStream& s) = 0; + + /** + * Write the value into a stream. MUST be overwritten + **/ + virtual void save(QDataStream& s) = 0; + + /** + * send a command to advanced properties like arrays + * @param stream The stream containing the data of the comand + * @param msgid The ID of the command - see PropertyCommandIds + * @param isSender whether this client is also the sender of the command + **/ + virtual void command(QDataStream &stream, int msgid, bool isSender=false); + + /** + * @return The id of this property + **/ + int id() const { return mId; } + + /** + * @return a type_info of the data this property contains. This is used + * e.g. by KGameDebugDialog + **/ + virtual const type_info* typeinfo() { return &typeid(this); } + + /** + * You have to register a KGamePropertyBase before you can use it. + * + * You MUST call this before you can use KGamePropertyBase! + * + * @param id the id of this KGamePropertyBase object. The id MUST be + * unique, i.e. you cannot have two properties with the same id for one + * player, although (currently) nothing prevents you from doing so. But + * you will get strange results! + * + * @param owner The owner of this data. This will send the data + * using KPropertyHandler::sendProperty whenever you call send + * + * @param p If not 0 you can set the policy of the property here + * + * @param name if not 0 you can assign a name to this property + * + **/ + int registerData(int id, KGamePropertyHandler* owner,PropertyPolicy p, QString name=0); + + /** + * This is an overloaded member function, provided for convenience. + * It differs from the above function only in what argument(s) it accepts. + **/ + int registerData(int id, KGamePropertyHandler* owner, QString name=0); + + /** + * This is an overloaded member function, provided for convenience. + * It differs from the above function only in what argument(s) it accepts. + **/ + int registerData(int id, KGame* owner, QString name=0); + + /** + * This is an overloaded member function, provided for convenience. + * It differs from the above function only in what argument(s) it accepts. + **/ + int registerData(int id, KPlayer* owner, QString name=0); + + /** + * This is an overloaded member function, provided for convenience. + * It differs from the above function only in what argument(s) it accepts. + * In particular you can use this function to create properties which + * will have an automatic id assigned. The new id is returned. + **/ + int registerData(KGamePropertyHandler* owner,PropertyPolicy p=PolicyUndefined, QString name=0); + + void unregisterData(); + + +protected: + /** + * A locked property can only be changed by the player who has set the + * lock. + * + * You can only call this if isLocked is false. A message is sent + * over network so that the property is locked for all players except + * you. + * Usually you use lock and unlock to access this property + * + **/ + void setLock(bool l); + + /** + * Sets the "dirty" flag of the property. If a property is "dirty" i.e. + * KGameProperty::setLocal has been called there is no guarantee + * that all clients share the same value. You have to ensure this + * yourself e.g. by calling KGameProperty::setLocal on every + * client. You can also ignore the dirty flag and continue working withe + * the property depending on your situation. + **/ + void setDirty(bool d) { mFlags.bits.dirty = d ; } + + /** + * Forward the data to the owner of this property which then sends it + * over network. save is used to store the data into a stream so + * you have to make sure that function is working properly if you + * implement your own property! + * + * Note: this sends the <em>current</em> property! + * + * Might be obsolete - KGamePropertyArray still uses it. Is this a bug + * or correct? + **/ + bool sendProperty(); + + /** + * Forward the data to the owner of this property which then sends it + * over network. save is used to store the data into a stream so + * you have to make sure that function is working properly if you + * implement your own property! + * + * This function is used by send to send the data over network. + * This does <em>not</em> send the current value but the explicitly + * given value. + * + * @return TRUE if the message could be sent successfully, otherwise + * FALSE + **/ + bool sendProperty(const QByteArray& b); + + /** + * Causes the parent object to emit a signal on value change + **/ + void emitSignal(); + +protected: + KGamePropertyHandler* mOwner; + + // Having this as a union of the bitfield and the char + // allows us to stream this quantity easily (if we need to) + // At the moment it is not yet transmitted + union Flags { + char flag; + struct { + // unsigned char dosave : 1; // do save this property + // unsigned char delaytransmit : 1; // do not send immediately on + // change but a KPlayer:QTimer + // sends it later on - fast + // changing variables + unsigned char emitsignal : 1; // KPlayer notifies on variable change (true) + //unsigned char readonly : 1; // whether the property can be changed (false) + unsigned char optimize : 1; // whether the property tries to optimize send/emit (false) + unsigned char dirty: 1; // whether the property dirty (setLocal() was used) + unsigned char policy : 2; // whether the property is always consistent (see PropertyPolicy) + unsigned char locked: 1; // whether the property is locked (true) + } bits; + } mFlags; + +private: + friend class KGamePropertyHandler; + void init(); + +private: + int mId; + +}; + +/** + * @short A class for network transparent games + * + * Note: The entire API documentation is obsolete! + * + * The class KGameProperty can store any form of data and will transmit it via + * network whenver you call send. This makes network transparent games + * very easy. You first have to register the data to a KGamePropertyHandler + * using KGamePropertyBase::registerData (which is called by the + * constructor). For the KGamePropertyHandler you can use + * KGame::dataHandler or KPlayer::dataHandler but you can also create your + * own data handler. + * + * There are several concepts you can follow when writing network games. These + * concepts differ completely from the way how data is transferred so you should + * decide which one to use. You can also mix these concepts for a single + * property but we do not recommend this. The concepts: + * <ul> + * <li> Always Consistent (clean) + * <li> Not Always Consistent (dirty) + * <li> A Mixture (very dirty) + * </ul> + * I repeat: we do <em>not</em> recommend the third option ("a mixture"). Unless + * you have a good reason for this you will probably introduce some hard to find + * (and to fix) bugs. + * + * @section Always consistent (clean): + * + * This "policy" is default. Whenever you create a KGameProperty it is always + * consistent. This means that consistency is the most important thing for the + * property. This is achieved by using send to change the value of the + * property. send needs a running KMessageServer and therefore + * <em>MUST</em> be plugged into a KGamePropertyHandler using either + * registerData or the constructor. The parent of the dataHandler must be able + * to send messages (see above: the message server must be running). If you use + * send to change the value of a property you won't see the effect + * immediately: The new value is first transferred to the message server which + * queues the message. As soon as <em>all</em> messages in the message server + * which are before the changed property have been transferred the message + * server delivers the new value of the KGameProperty to all clients. A + * QTimer::singleShot is used to queue the messages inside the + * KMessageServer. + * + * This means that if you do the following: + * \code + * KGamePropertyInt myProperty(id, dataHandler()); + * myProperty.initData(0); + * myProperty = 10; + * int value = myProperty.value(); + * \endcode + * then "value" will be "0". initData is used to initialize the property + * (e.g. when the KMessageServer is not yet running or can not yet be + * reached). This is because "myProperty = 10" or "myProperty.send(10)" send a + * message to the KMessageServer which uses QTimer::singleShot to + * queue the message. The game first has to go back into the event loop where + * the message is received. The KGamePropertyHandler receives the new value + * sets the property. So if you need the new value you need to store it in a + * different variable (see setLocal which creates one for you until the + * message is received). The KGamePropertyHandler emits a signal (unless + * you called setEmitSignal with false) when the new value is received: + * KGamePropertyHandler::signalPropertyChanged. You can use this to react + * to a changed property. + * + * This may look quite confusing but it has a <em>big</em> advantage: all + * KGameProperty objects are ensured to have the same value on all clients in + * the game at every time. This way you will save you a lot of trouble as + * debugging can be very difficult if the value of a property changes + * immediately on client A but only after one or two additianal messages + * (function calls, status changes, ...) on client B. + * + * The only disadvantage of this (clean) concept is that you cannot use a + * changed variable immediately but have to wait for the KMessageServer to + * change it. You probably want to use + * KGamePropertyHandler::signalPropertyChanged for this. + * + * @section Not Always Consistent (dirty): + * + * There are a lot of people who don't want to use the (sometimes quite complex) + * "clean" way. You can use setAlwaysConsistent to change the default + * behaviour of the KGameProperty. If a property is not always consistent + * it will use changeValue to send the property. changeValue also uses + * send to send the new value over network but it also uses + * setLocal to create a local copy of the property. This copy is created + * dynamically and is deleted again as soon as the next message from the network + * is received. To use the example above again: + * \code + * KGamePropertyInt myProperty(id, dataHandler()); + * myProperty.setAlwaysConsistent(false); + * myProperty.initData(0); + * myProperty = 10; + * int value = myProperty.value(); + * \endcode + * Now this example will "work" so value now is 10. Additionally the + * KMessageServer receives a message from the local client (just as explained + * above in "Always Consistent"). As soon as the message returns to the local + * client again the local value is deleted, as the "network value" has the same + * value as the local one. So you won't lose the ability to use the always + * consistent "clean" value of the property if you use the "dirty" way. Just use + * networkValue to access the value which is consistent among all clients. + * + * The advantage of this concept is clear: you can use a KGameProperty as + * every other variable as the changes value takes immediate effect. + * Additionally you can be sure that the value is transferred to all clients. + * You will usually not experience serious bugs just because you use the "dirty" + * way. Several events have to happen at once to get these "strange errors" + * which result in inconsistent properties (like "game running" on client A but + * "game ended/paused" on client B). But note that there is a very good reason + * for the existence of these different concepts of KGameProperty. I have + * myself experienced such a "strange error" and it took me several days to find + * the reason until I could fix it. So I personally recommend the "clean" way. + * On the other hand if you want to port a non-network game to a network game + * you will probably start with "dirty" properties as it is you will not have to + * change that much code... + * + * @section A Mixture (very dirty): + * + * You can also mix the concepts above. Note that we really don't recommend + * this. With a mixture I mean something like this: + * \code + * KGamePropertyInt myProperty(id, dataHandler()); + * myProperty.setAlwaysConsistent(false); + * myProperty.initData(0); + * myProperty = 10; + * myProperty.setAlwaysConsistent(true); + * myProperty = 20; + * \endcode + * (totally senseless example, btw) I.e. I am speaking of mixing both concepts + * for a single property. Things like + * \code + * KGamePropertyInt myProperty1(id1, dataHandler()); + * KGamePropertyInt myProperty2(id2, dataHandler()); + * myProperty1.initData(0); + * myProperty2.initData(0); + * myProperty1.setAlwaysConsistent(false); + * myProperty2.setAlwaysConsistent(true); + * myProperty1 = 10; + * myProperty2 = 20; + * \endcode + * are ok. But mixing the concepts for a single property will make it nearly + * impossible to you to debug your game. + * + * So the right thing to do(tm) is to decide in the constructor whether you want + * a "clean" or "dirty" property. + * + * Even if you have decided for one of the concepts you still can manually + * follow another concept than the "policy" of your property. So if you use an + * always consistent KGameProperty you still can manually call + * changeValue as if it was not always consistent. Note that although this is + * also kind of a "mixture" as described above this is very useful sometimes. In + * contrast to the "mixture" above you don't have the problem that you don't + * exactly know which concept you are currently following because you used the + * function of the other concept only once. + * + * @section Custom classes: + * + * If you want to use a custum class with KGameProperty you have to implement the + * operators << and >> for QDataStream: + * \code + * class Card + * { + * public: + * int type; + * int suite; + * }; + * QDataStream& operator<<(QDataStream& stream, Card& card) + * { + * Q_INT16 type = card.type; + * Q_INT16 suite = card.suite; + * s << type; + * s << suite; + * return s; + * } + * QDataStream& operator>>(QDataStream& stream, Card& card) + * { + * Q_INT16 type; + * Q_INT16 suite; + * s >> type; + * s >> suite; + * card.type = (int)type; + * card.suite = (int)suite; + * return s; + * } + * + * class Player : KPlayer + * { + * [...] + * KGameProperty<Card> mCards; + * }; + * \endcode + * + * Note: unlike most QT classes KGameProperty objects are *not* deleted + * automatically! So if you create an object using e.g. KGameProperty<int>* data = + * new KGameProperty(id, dataHandler()) you have to put a delete data into your + * destructor! + * + * @author Andreas Beckermann <[email protected]> + **/ +template<class type> +class KGameProperty : public KGamePropertyBase +{ +public: + /** + * Constructs a KGameProperty object. A KGameProperty object will transmit + * any changes to the KMessageServer and then to all clients in the + * game (including the one that has sent the new value) + * @param id The id of this property. <em>MUST be UNIQUE</em>! Used to send and + * receive changes in the property of the playere automatically via + * network. + * @param owner The parent of the object. Must be a KGame which manages + * the changes made to this object, i.e. which will send the new data. + * Note that in contrast to most KDE/QT classes KGameProperty objects + * are <em>not</em> deleted automatically! + **/ +// TODO: ID: Very ugly - better use something like parent()->propertyId() or so which assigns a free id automatically. + KGameProperty(int id, KGamePropertyHandler* owner) : KGamePropertyBase(id, owner) { init(); } + + /** + * This constructor does nothing. You have to call + * KGamePropertyBase::registerData + * yourself before using the KGameProperty object. + **/ + KGameProperty() : KGamePropertyBase() { init(); } + + virtual ~KGameProperty() {} + + /** + * Set the value depending on the current policy (see + * setConsistent). By default KGameProperty just uses send to set + * the value of a property. This behaviour can be changed by using + * setConsistent. + * @param v The new value of the property + **/ + void setValue(type v) + { + switch (policy()) { + case PolicyClean: + send(v); + break; + case PolicyDirty: + changeValue(v); + break; + case PolicyLocal: + setLocal(v); + break; + default: // NEVER! + kdError(11001) << "Undefined Policy in property " << id() << endl; + return; + } + } + + + /** + * This function sends a new value over network. + * + * Note that the value DOES NOT change when you call this function. This + * function saves the value into a QDataStream and calls + * sendProperty where it gets forwarded to the owner and finally the + * value is sent over network. The KMessageServer now sends the + * value to ALL clients - even the one who called this function. As soon + * as the value from the message server is received load is called + * and _then_ the value of the KGameProperty has been set. + * + * This ensures that a KGameProperty has _always_ the same value on + * _every_ client in the network. Note that this means you can NOT do + * something like + * \code + * myProperty.send(1); + * doSomething(myProperty); + * \endcode + * as myProperty has not yet been set when doSomething is being called. + * + * You are informed about a value change by a singal from the parent of + * the property which can be deactivated by setEmittingSignal because of + * performance (you probably don't have to deactivate it - except you + * want to write a real-time game like Command&Conquer with a lot of + * acitvity). See emitSignal + * + * Note that if there is no KMessageServer accessible - before + * the property has been registered to the KGamePropertyHandler (as + * it is the case e.g. before a KPlayer has been plugged into the + * KGame object) the property is *not* sent but set *locally* (see + * setLocal)! + * + * @param v The new value of the property + * @return whether the property could be sent successfully + * @see setValue setLocal changeValue value + **/ + bool send(type v) + { + if (isOptimized() && mData == v) { + return true; + } + if (isLocked()) { + return false; + } + QByteArray b; + QDataStream stream(b, IO_WriteOnly); + stream << v; + if (!sendProperty(b)) { + setLocal(v); + return false; + } + return true; + } + + /** + * This function sets the value of the property directly, i.e. it + * doesn't send it to the network. + * + * Int contrast to @see you change _only_ the local value when using + * this function. You do _not_ change the value of any other client. You + * probably don't want to use this if you are using a dedicated server + * (which is the only "client" which is allowed to change a value) but + * rather want to use send(). + * + * But if you use your clients as servers (i.e. all clients receive a + * players turn and then calculate the reaction of the game theirselves) + * then you probably want to use setLocal as you can do things like + * \code + * myProperty.setLocal(1); + * doSomething(myProperty); + * \endcode + * on every client. + * + * If you want to set the value locally AND send it over network you + * want to call changeValue! + * + * You can also use setPolicy to set the default policy to + * PolicyLocal. + * + * @see setValue send changeValue value + **/ + bool setLocal(type v) + { + if (isOptimized() && mData == v) { + return false; + } + if (isLocked()) { + return false; + } + mData = v; + setDirty(true); + if (isEmittingSignal()) { + emitSignal(); + } + return true; + } + + /** + * This function does both, change the local value and change the + * network value. The value is sent over network first, then changed + * locally. + * + * This function is a convenience function and just calls send + * followed by setLocal + * + * Note that emitSignal is also called twice: once after + * setLocal and once when the value from send is received + * + * @see send setLocal setValue value + **/ + void changeValue(type v) + { + send(v); + setLocal(v); + } + + /** + * Saves the object to a stream. + * @param stream The stream to save to + **/ + virtual void save(QDataStream &stream) + { + stream << mData; + } + + /** + * @return The local value (see setLocal) if it is existing, + * otherwise the network value which is always consistent on every + * client. + **/ + const type& value() const + { + return mData; + } + + /** + * Reads from a stream and assigns the read value to this object. + * + * This function is called automatically when a new value is received + * over network (i.e. it has been sent using send on this or any + * other client) or when a game is loaded (and maybe on some other + * events). + * + * Also calls emitSignal if isEmittingSignal is TRUE. + * @param s The stream to read from + **/ + virtual void load(QDataStream& s) + { + s >> mData; + setDirty(false); + if (isEmittingSignal()) { + emitSignal(); + } + } + + /** + * This calls setValue to change the value of the property. Note + * that depending on the policy (see setAlwaysConsistent) the + * returned value might be different from the assigned value!! + * + * So if you use setPolicy(PolicyClean): + * \code + * int a, b = 10; + * myProperty = b; + * a = myProperty.value(); + * \endcode + * Here a and b would differ! + * The value is actually set as soon as it is received from the + * KMessageServer which forwards it to ALL clients in the network. + * + * If you use a clean policy (see setPolicy) then + * the returned value is the assigned value + **/ + const type& operator=(const type& t) + { + setValue(t); + return value(); + } + + /** + * This copies the data of property to the KGameProperty object. + * + * Equivalent to setValue(property.value()); + **/ + const type& operator=(const KGameProperty& property) + { + setValue(property.value()); + return value(); + } + + /** + * Yeah, you can do it! + * \code + * int a = myGamePropertyInt; + * \endcode + * If you don't see it: you don't have to use integerData.value() + **/ + operator type() const { return value(); } + + virtual const type_info* typeinfo() { return &typeid(type); } + +private: + void init() { } + +private: + type mData; +}; + + +typedef KGameProperty<int> KGamePropertyInt; +typedef KGameProperty<unsigned int> KGamePropertyUInt; +typedef KGameProperty<QString> KGamePropertyQString; +typedef KGameProperty<Q_INT8> KGamePropertyBool; + +#endif diff --git a/libkdegames/kgame/kgamepropertyarray.h b/libkdegames/kgame/kgamepropertyarray.h new file mode 100644 index 00000000..f91bd75c --- /dev/null +++ b/libkdegames/kgame/kgamepropertyarray.h @@ -0,0 +1,309 @@ +/* + 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. +*/ + +#ifndef __KGAMEPROPERTYARRAY_H_ +#define __KGAMEPROPERTYARRAY_H_ + +#include <qdatastream.h> +#include <kdebug.h> + +#include "kgamemessage.h" +#include "kgameproperty.h" +#include "kgamepropertyhandler.h" + + +template<class type> +class KGamePropertyArray : public QMemArray<type>, public KGamePropertyBase +{ +public: + KGamePropertyArray() :QMemArray<type>(), KGamePropertyBase() + { + //kdDebug(11001) << "KGamePropertyArray init" << endl; + } + + KGamePropertyArray( int size ) + { + resize(size); + } + + KGamePropertyArray( const KGamePropertyArray<type> &a ) : QMemArray<type>(a) + { + } + + bool resize( uint size ) + { + if (size!=QMemArray<type>::size()) + { + bool a=true; + QByteArray b; + QDataStream s(b, IO_WriteOnly); + KGameMessage::createPropertyCommand(s,KGamePropertyBase::IdCommand,id(),CmdResize); + s << size ; + if (policy()==PolicyClean || policy()==PolicyDirty) + { + if (mOwner) + { + mOwner->sendProperty(s); + } + } + if (policy()==PolicyLocal || policy()==PolicyDirty) + { + extractProperty(b); +// a=QMemArray<type>::resize(size);// FIXME: return value! + } + return a; + } + else return true; + } + + void setAt(uint i,type data) + { + QByteArray b; + QDataStream s(b, IO_WriteOnly); + KGameMessage::createPropertyCommand(s,KGamePropertyBase::IdCommand,id(),CmdAt); + s << i ; + s << data; + if (policy()==PolicyClean || policy()==PolicyDirty) + { + if (mOwner) + { + mOwner->sendProperty(s); + } + } + if (policy()==PolicyLocal || policy()==PolicyDirty) + { + extractProperty(b); + } + //kdDebug(11001) << "KGamePropertyArray setAt send COMMAND for id="<<id() << " type=" << 1 << " at(" << i<<")="<<data << endl; + } + + type at( uint i ) const + { + return QMemArray<type>::at(i); + } + + type operator[]( int i ) const + { + return QMemArray<type>::at(i); + } + + KGamePropertyArray<type> &operator=(const KGamePropertyArray<type> &a) + { + return assign(a); + } + + bool truncate( uint pos ) + { + return resize(pos); + } + + bool fill( const type &data, int size = -1 ) + { + bool r=true; + QByteArray b; + QDataStream s(b, IO_WriteOnly); + KGameMessage::createPropertyCommand(s,KGamePropertyBase::IdCommand,id(),CmdFill); + s << data; + s << size ; + if (policy()==PolicyClean || policy()==PolicyDirty) + { + if (mOwner) + { + mOwner->sendProperty(s); + } + } + if (policy()==PolicyLocal || policy()==PolicyDirty) + { + extractProperty(b); +// r=QMemArray<type>::fill(data,size);//FIXME: return value! + } + return r; + } + + KGamePropertyArray<type>& assign( const KGamePropertyArray<type>& a ) + { +// note: send() has been replaced by sendProperty so it might be broken now! + if (policy()==PolicyClean || policy()==PolicyDirty) + { + sendProperty(); + } + if (policy()==PolicyLocal || policy()==PolicyDirty) + { + QMemArray<type>::assign(a); + } + return *this; + } + KGamePropertyArray<type>& assign( const type *a, uint n ) + { + if (policy()==PolicyClean || policy()==PolicyDirty) + { + sendProperty(); + } + if (policy()==PolicyLocal || policy()==PolicyDirty) + { + QMemArray<type>::assign(a,n); + } + return *this; + } + KGamePropertyArray<type>& duplicate( const KGamePropertyArray<type>& a ) + { + if (policy()==PolicyClean || policy()==PolicyDirty) + { + sendProperty(); + } + if (policy()==PolicyLocal || policy()==PolicyDirty) + { + QMemArray<type>::duplicate(a); + } + return *this; + } + KGamePropertyArray<type>& duplicate( const type *a, uint n ) + { + if (policy()==PolicyClean || policy()==PolicyDirty) + { + sendProperty(); + } + if (policy()==PolicyLocal || policy()==PolicyDirty) + { + QMemArray<type>::duplicate(a,n); + } + return *this; + } + KGamePropertyArray<type>& setRawData( const type *a, uint n ) + { + if (policy()==PolicyClean || policy()==PolicyDirty) + { + sendProperty(); + } + if (policy()==PolicyLocal || policy()==PolicyDirty) + { + QMemArray<type>::setRawData(a,n); + } + return *this; + } + void sort() + { + QByteArray b; + QDataStream s(b, IO_WriteOnly); + KGameMessage::createPropertyCommand(s,KGamePropertyBase::IdCommand,id(),CmdSort); + if (policy()==PolicyLocal || policy()==PolicyDirty) + { + if (mOwner) + { + mOwner->sendProperty(s); + } + } + if (policy()==PolicyLocal || policy()==PolicyDirty) + { + extractProperty(b); + } + } + + void load(QDataStream& s) + { + //kdDebug(11001) << "KGamePropertyArray load " << id() << endl; + type data; + for (unsigned int i=0; i<QMemArray<type>::size(); i++) + { + s >> data; + QMemArray<type>::at(i)=data; + } + if (isEmittingSignal()) + { + emitSignal(); + } + } + void save(QDataStream &s) + { + //kdDebug(11001) << "KGamePropertyArray save "<<id() << endl; + for (unsigned int i=0; i<QMemArray<type>::size(); i++) + { + s << at(i); + } + } + + void command(QDataStream &s,int cmd,bool) + { + KGamePropertyBase::command(s, cmd); + //kdDebug(11001) << "Array id="<<id()<<" got command ("<<cmd<<") !!!" <<endl; + switch(cmd) + { + case CmdAt: + { + uint i; + type data; + s >> i >> data; + QMemArray<type>::at(i)=data; + //kdDebug(11001) << "CmdAt:id="<<id()<<" i="<<i<<" data="<<data <<endl; + if (isEmittingSignal()) + { + emitSignal(); + } + break; + } + case CmdResize: + { + uint size; + s >> size; + //kdDebug(11001) << "CmdResize:id="<<id()<<" oldsize="<<QMemArray<type>::size()<<" newsize="<<size <<endl; + if (QMemArray<type>::size() != size) + { + QMemArray<type>::resize(size); + } + break; + } + case CmdFill: + { + int size; + type data; + s >> data >> size; + //kdDebug(11001) << "CmdFill:id="<<id()<<"size="<<size <<endl; + QMemArray<type>::fill(data,size); + if (isEmittingSignal()) + { + emitSignal(); + } + break; + } + case CmdSort: + { + //kdDebug(11001) << "CmdSort:id="<<id()<<endl; + QMemArray<type>::sort(); + break; + } + default: + kdError(11001) << "Error in KPropertyArray::command: Unknown command " << cmd << endl; + break; + } + } +protected: + void extractProperty(const QByteArray& b) + { + QDataStream s(b, IO_ReadOnly); + int cmd; + int propId; + KGameMessage::extractPropertyHeader(s, propId); + KGameMessage::extractPropertyCommand(s, propId, cmd); + command(s, cmd, true); + } + +}; + +#endif diff --git a/libkdegames/kgame/kgamepropertyhandler.cpp b/libkdegames/kgame/kgamepropertyhandler.cpp new file mode 100644 index 00000000..75b3e7e2 --- /dev/null +++ b/libkdegames/kgame/kgamepropertyhandler.cpp @@ -0,0 +1,407 @@ +/* + This file is part of the KDE games library + Copyright (C) 2001 Andreas Beckermann ([email protected]) + Copyright (C) 2001 Martin Heni ([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 "kgamepropertyhandler.h" +#include "kgameproperty.h" +#include "kgamemessage.h" + +#include <qmap.h> +#include <qptrqueue.h> + +#include <klocale.h> +#include <typeinfo> + +#define KPLAYERHANDLER_LOAD_COOKIE 6239 + +//---------------------- KGamePropertyHandler ----------------------------------- +class KGamePropertyHandlerPrivate +{ +public: + KGamePropertyHandlerPrivate() + { + } + + QMap<int, QString> mNameMap; + QIntDict<KGamePropertyBase> mIdDict; + int mUniqueId; + int mId; + KGamePropertyBase::PropertyPolicy mDefaultPolicy; + bool mDefaultUserspace; + int mIndirectEmit; + QPtrQueue<KGamePropertyBase> mSignalQueue; +}; + +KGamePropertyHandler::KGamePropertyHandler(int id, const QObject* receiver, const char * sendf, const char *emitf, QObject* parent) : QObject(parent) +{ + init(); + registerHandler(id,receiver,sendf,emitf); +} + +KGamePropertyHandler::KGamePropertyHandler(QObject* parent) : QObject(parent) +{ + init(); +} + +KGamePropertyHandler::~KGamePropertyHandler() +{ + clear(); + delete d; +} + +void KGamePropertyHandler::init() +{ + kdDebug(11001) << k_funcinfo << ": this=" << this << endl; + d = new KGamePropertyHandlerPrivate; // for future use - is BC important to us? + d->mId = 0; + d->mUniqueId=KGamePropertyBase::IdAutomatic; + d->mDefaultPolicy=KGamePropertyBase::PolicyLocal; + d->mDefaultUserspace=true; + d->mIndirectEmit=0; +} + + +int KGamePropertyHandler::id() const +{ + return d->mId; +} + +void KGamePropertyHandler::setId(int id) +{ + d->mId = id; +} + +void KGamePropertyHandler::registerHandler(int id,const QObject * receiver, const char * sendf, const char *emitf) +{ + setId(id); + if (receiver && sendf) { + kdDebug(11001) << "Connecting SLOT " << sendf << endl; + connect(this, SIGNAL(signalSendMessage(int, QDataStream &, bool*)), receiver, sendf); + } + if (receiver && emitf) { + kdDebug(11001) << "Connecting SLOT " << emitf << endl; + connect(this, SIGNAL(signalPropertyChanged(KGamePropertyBase *)), receiver, emitf); + } +} + +bool KGamePropertyHandler::processMessage(QDataStream &stream, int id, bool isSender) +{ +// kdDebug(11001) << k_funcinfo << ": id=" << id << " mId=" << d->mId << endl; + if (id != d->mId) { + return false; // Is the message meant for us? + } + KGamePropertyBase* p; + int propertyId; + KGameMessage::extractPropertyHeader(stream, propertyId); +// kdDebug(11001) << k_funcinfo << ": Got property " << propertyId << endl; + if (propertyId==KGamePropertyBase::IdCommand) { + int cmd; + KGameMessage::extractPropertyCommand(stream, propertyId, cmd); +//kdDebug(11001) << k_funcinfo << ": Got COMMAND for id= "<<propertyId <<endl; + p = d->mIdDict.find(propertyId); + if (p) { + if (!isSender || p->policy()==KGamePropertyBase::PolicyClean) { + p->command(stream, cmd, isSender); + } + } else { + kdError(11001) << k_funcinfo << ": (cmd): property " << propertyId << " not found" << endl; + } + return true; + } + p = d->mIdDict.find(propertyId); + if (p) { + //kdDebug(11001) << k_funcinfo << ": Loading " << propertyId << endl; + if (!isSender || p->policy()==KGamePropertyBase::PolicyClean) { + p->load(stream); + } + } else { + kdError(11001) << k_funcinfo << ": property " << propertyId << " not found" << endl; + } + return true; +} + +bool KGamePropertyHandler::removeProperty(KGamePropertyBase* data) +{ + if (!data) { + return false; + } + d->mNameMap.erase(data->id()); + return d->mIdDict.remove(data->id()); +} + +bool KGamePropertyHandler::addProperty(KGamePropertyBase* data, QString name) +{ + //kdDebug(11001) << k_funcinfo << ": " << data->id() << endl; + if (d->mIdDict.find(data->id())) { + // this id already exists + kdError(11001) << " -> cannot add property " << data->id() << endl; + return false; + } else { + d->mIdDict.insert(data->id(), data); + // if here is a check for "is_debug" or so we can add the strings only in debug mode + // and save memory!! + if (!name.isNull()) { + d->mNameMap[data->id()] = name; + //kdDebug(11001) << k_funcinfo << ": nid="<< (data->id()) << " inserted in Map name=" << d->mNameMap[data->id()] <<endl; + //kdDebug(11001) << "Typeid=" << typeid(data).name() << endl; + //kdDebug(11001) << "Typeid call=" << data->typeinfo()->name() << endl; + } + } + return true; +} + +QString KGamePropertyHandler::propertyName(int id) const +{ + QString s; + if (d->mIdDict.find(id)) { + if (d->mNameMap.contains(id)) { + s = i18n("%1 (%2)").arg(d->mNameMap[id]).arg(id); + } else { + s = i18n("Unnamed - ID: %1").arg(id); + } + } else { + // Should _never_ happen + s = i18n("%1 unregistered").arg(id); + } + return s; +} + +bool KGamePropertyHandler::load(QDataStream &stream) +{ + // Prevent direct emmiting until all is loaded + lockDirectEmit(); + uint count,i; + stream >> count; + kdDebug(11001) << k_funcinfo << ": " << count << " KGameProperty objects " << endl; + for (i = 0; i < count; i++) { + processMessage(stream, id(),false); + } + Q_INT16 cookie; + stream >> cookie; + if (cookie == KPLAYERHANDLER_LOAD_COOKIE) { + kdDebug(11001) << " KGamePropertyHandler loaded propertly"<<endl; + } else { + kdError(11001) << "KGamePropertyHandler loading error. probably format error"<<endl; + } + // Allow direct emmiting (if no other lock still holds) + unlockDirectEmit(); + return true; +} + +bool KGamePropertyHandler::save(QDataStream &stream) +{ + kdDebug(11001) << k_funcinfo << ": " << d->mIdDict.count() << " KGameProperty objects " << endl; + stream << (uint)d->mIdDict.count(); + QIntDictIterator<KGamePropertyBase> it(d->mIdDict); + while (it.current()) { + KGamePropertyBase *base=it.current(); + if (base) { + KGameMessage::createPropertyHeader(stream, base->id()); + base->save(stream); + } + ++it; + } + stream << (Q_INT16)KPLAYERHANDLER_LOAD_COOKIE; + return true; +} + +KGamePropertyBase::PropertyPolicy KGamePropertyHandler::policy() +{ +// kdDebug(11001) << k_funcinfo << ": " << d->mDefaultPolicy << endl; + return d->mDefaultPolicy; +} +void KGamePropertyHandler::setPolicy(KGamePropertyBase::PropertyPolicy p,bool userspace) +{ + // kdDebug(11001) << k_funcinfo << ": " << p << endl; + d->mDefaultPolicy=p; + d->mDefaultUserspace=userspace; + QIntDictIterator<KGamePropertyBase> it(d->mIdDict); + while (it.current()) { + if (!userspace || it.current()->id()>=KGamePropertyBase::IdUser) { + it.current()->setPolicy((KGamePropertyBase::PropertyPolicy)p); + } + ++it; + } +} + +void KGamePropertyHandler::unlockProperties() +{ + QIntDictIterator<KGamePropertyBase> it(d->mIdDict); + while (it.current()) { + it.current()->unlock(); + ++it; + } +} + +void KGamePropertyHandler::lockProperties() +{ + QIntDictIterator<KGamePropertyBase> it(d->mIdDict); + while (it.current()) { + it.current()->lock(); + ++it; + } +} + +int KGamePropertyHandler::uniquePropertyId() +{ + return d->mUniqueId++; +} + +void KGamePropertyHandler::flush() +{ + QIntDictIterator<KGamePropertyBase> it(d->mIdDict); + while (it.current()) { + if (it.current()->isDirty()) { + it.current()->sendProperty(); + } + ++it; + } +} + +/* Fire all property signal changed which are collected in + * the queque + **/ +void KGamePropertyHandler::lockDirectEmit() +{ + d->mIndirectEmit++; +} + +void KGamePropertyHandler::unlockDirectEmit() +{ + // If the flag is <=0 we emit the queued signals + d->mIndirectEmit--; + if (d->mIndirectEmit<=0) + { + KGamePropertyBase *prop; + while((prop=d->mSignalQueue.dequeue()) != 0) + { + // kdDebug(11001) << "emmiting signal for " << prop->id() << endl; + emit signalPropertyChanged(prop); + } + } +} + +void KGamePropertyHandler::emitSignal(KGamePropertyBase *prop) +{ + // If the indirect flag is set (load and network transmit) + // we cannot emit the signals directly as it can happend that + // a sigal causes an access to a property which is e.g. not + // yet loaded or received + + if (d->mIndirectEmit>0) + { + // Queque the signal + d->mSignalQueue.enqueue(prop); + } + else + { + // directly emit + emit signalPropertyChanged(prop); + } +} + +bool KGamePropertyHandler::sendProperty(QDataStream &s) +{ + bool sent = false; + emit signalSendMessage(id(), s, &sent); + return sent; +} + +KGamePropertyBase *KGamePropertyHandler::find(int id) +{ + return d->mIdDict.find(id); +} + +void KGamePropertyHandler::clear() +{ + kdDebug(11001) << k_funcinfo << id() << endl; + QIntDictIterator<KGamePropertyBase> it(d->mIdDict); + while (it.toFirst()) { + KGamePropertyBase* p = it.toFirst(); + p->unregisterData(); + if (d->mIdDict.find(p->id())) { + // shouldn't happen - but if mOwner in KGamePropertyBase is NULL + // this might be possible + removeProperty(p); + } + } +} + +QIntDict<KGamePropertyBase>& KGamePropertyHandler::dict() const +{ + return d->mIdDict; +} + +QString KGamePropertyHandler::propertyValue(KGamePropertyBase* prop) +{ + if (!prop) { + return i18n("NULL pointer"); + } + + int id = prop->id(); + QString name = propertyName(id); + QString value; + + const type_info* t = prop->typeinfo(); + if (*t == typeid(int)) { + value = QString::number(((KGamePropertyInt*)prop)->value()); + } else if (*t == typeid(unsigned int)) { + value = QString::number(((KGamePropertyUInt *)prop)->value()); + } else if (*t == typeid(long int)) { + value = QString::number(((KGameProperty<long int> *)prop)->value()); + } else if (*t == typeid(unsigned long int)) { + value = QString::number(((KGameProperty<unsigned long int> *)prop)->value()); + } else if (*t == typeid(QString)) { + value = ((KGamePropertyQString*)prop)->value(); + } else if (*t == typeid(Q_INT8)) { + value = ((KGamePropertyBool*)prop)->value() ? i18n("True") : i18n("False"); + } else { + emit signalRequestValue(prop, value); + } + + if (value.isNull()) { + value = i18n("Unknown"); + } + return value; +} + +void KGamePropertyHandler::Debug() +{ + kdDebug(11001) << "-----------------------------------------------------------" << endl; + kdDebug(11001) << "KGamePropertyHandler:: Debug this=" << this << endl; + + kdDebug(11001) << " Registered properties: (Policy,Lock,Emit,Optimized, Dirty)" << endl; + QIntDictIterator<KGamePropertyBase> it(d->mIdDict); + while (it.current()) { + KGamePropertyBase *p=it.current(); + kdDebug(11001) << " "<< p->id() << ": p=" << p->policy() + << " l="<<p->isLocked() + << " e="<<p->isEmittingSignal() + << " o=" << p->isOptimized() + << " d="<<p->isDirty() + << endl; + ++it; + } + kdDebug(11001) << "-----------------------------------------------------------" << endl; +} + +#include "kgamepropertyhandler.moc" diff --git a/libkdegames/kgame/kgamepropertyhandler.h b/libkdegames/kgame/kgamepropertyhandler.h new file mode 100644 index 00000000..6147c071 --- /dev/null +++ b/libkdegames/kgame/kgamepropertyhandler.h @@ -0,0 +1,353 @@ +/* + This file is part of the KDE games library + Copyright (C) 2001 Andreas Beckermann ([email protected]) + Copyright (C) 2001 Martin Heni ([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. +*/ + +#ifndef __KGAMEPROPERTYHANDLER_H_ +#define __KGAMEPROPERTYHANDLER_H_ + +#include <qobject.h> +#include <qintdict.h> + +#include "kgameproperty.h" +#include <kdemacros.h> + +class QDataStream; +class KGame; +class KPlayer; +//class KGamePropertyBase; + +class KGamePropertyHandlerPrivate; // wow - what a name ;-) + +/** + * @short A collection class for KGameProperty objects + * + * The KGamePropertyHandler class is some kind of a collection class for + * KGameProperty. You usually don't have to create one yourself, as both + * KPlayer and KGame provide a handler. In most cases you do not even have + * to care about the KGamePropertHandler. KGame and KPlayer implement + * all features of KGamePropertyHandler so you will rather use it there. + * + * You have to use the KGamePropertyHandler as parent for all KGameProperty + * objects but you can also use KPlayer or KGame as parent - then + * KPlayer::dataHandler or KGame::dataHandler will be used. + * + * Every KGamePropertyHandler must have - just like every KGameProperty - + * a unique ID. This ID is provided either in the constructor or in + * registerHandler. The ID is used to assign an incoming message (e.g. a changed + * property) to the correct handler. Inside the handler the property ID is used + * to change the correct property. + * + * The constructor or registerHandler takes 3 addittional arguments: a + * receiver and two slots. The first slot is connected to + * signalSendMessage, the second to signalPropertyChanged. You must provide + * these in order to use the KGamePropertyHandler. + * + * The most important function of KGamePropertyHandler is processMessage + * which assigns an incoming value to the correct property. + * + * A KGamePropertyHandler is also used - indirectly using emitSignal - to + * emit a signal when the value of a property changes. This is done this way + * because a KGameProperty does not inherit QObject because of memory + * advantages. Many games can have dozens or even hundreds of KGameProperty + * objects so every additional variable in KGameProperty would be + * multiplied. + * + **/ +class KDE_EXPORT KGamePropertyHandler : public QObject +{ + Q_OBJECT + +public: + /** + * Construct an unregistered KGamePropertyHandler + * + * You have to call registerHandler before you can use this + * handler! + **/ + KGamePropertyHandler(QObject* parent = 0); + + /** + * Construct a registered handler. + * + * @see registerHandler + **/ + KGamePropertyHandler(int id, const QObject* receiver, const char* sendf, const char* emitf, QObject* parent = 0); + ~KGamePropertyHandler(); + + /** + * Register the handler with a parent. This is to use + * if the constructor without arguments has been chosen. + * Otherwise you need not call this. + * + * @param id The id of the message to listen for + * @param receiver The object that will receive the signals of + * KGamePropertyHandler + * @param send A slot that is being connected to signalSendMessage + * @param emit A slot that is being connected to signalPropertyChanged + **/ + void registerHandler(int id, const QObject *receiver, const char * send, const char *emit); + + /** + * Main message process function. This has to be called by + * the parent's message event handler. If the id of the message + * agrees with the id of the handler, the message is extracted + * and processed. Otherwise false is returned. + * Example: + * \code + * if (mProperties.processMessage(stream,msgid,sender==gameId())) return ; + * \endcode + * + * @param stream The data stream containing the message + * @param id the message id of the message + * @param isSender Whether the receiver is also the sender + * @return true on message processed otherwise false + **/ + bool processMessage(QDataStream &stream, int id, bool isSender ); + + /** + * @return the id of the handler + **/ + int id() const; + + /** + * Adds a KGameProperty property to the handler + * @param data the property + * @param name A description of the property, which will be returned by + * propertyName. This is used for debugging, e.g. in KGameDebugDialog + * @return true on success + **/ + bool addProperty(KGamePropertyBase *data, QString name=0); + + /** + * Removes a property from the handler + * @param data the property + * @return true on success + **/ + bool removeProperty(KGamePropertyBase *data); + + /** + * returns a unique property ID starting called usually with a base of + * KGamePropertyBase::IdAutomatic. This is used internally by + * the property base to assign automtic id's. Not much need to + * call this yourself. + **/ + int uniquePropertyId(); + + + /** + * Loads properties from the datastream + * + * @param stream the datastream to load from + * @return true on success otherwise false + **/ + virtual bool load(QDataStream &stream); + + /** + * Saves properties into the datastream + * + * @param stream the datastream to save to + * @return true on success otherwise false + **/ + virtual bool save(QDataStream &stream); + + /** + * called by a property to send itself into the + * datastream. This call is simply forwarded to + * the parent object + **/ + bool sendProperty(QDataStream &s); + + void sendLocked(bool l); + + /** + * called by a property to emit a signal + * This call is simply forwarded to + * the parent object + **/ + void emitSignal(KGamePropertyBase *data); + + /** + * @param id The ID of the property + * @return A name of the property which can be used for debugging. Don't + * depend on this function! It it possible not to provide a name or to + * provide the same name for multiple properties! + **/ + QString propertyName(int id) const; + + /** + * @param id The ID of the property. See KGamePropertyBase::id + * @return The KGameProperty this ID is assigned to + **/ + KGamePropertyBase *find(int id); + + /** + * Clear the KGamePropertyHandler. Note that the properties are + * <em>not</em> deleted so if you created your KGameProperty + * objects dynamically like + * \code + * KGamePropertyInt* myProperty = new KGamePropertyInt(id, dataHandler()); + * \endcode + * you also have to delete it: + * \code + * dataHandler()->clear(); + * delete myProperty; + * \endcode + **/ + void clear(); + + /** + * Use id as new ID for this KGamePropertyHandler. This is used + * internally only. + **/ + void setId(int id);//AB: TODO: make this protected in KGamePropertyHandler!! + + /** + * Calls KGamePropertyBase::setReadOnly(false) for all properties of this + * player. See also lockProperties + **/ + void unlockProperties(); + + /** + * Set the policy for all kgame variables which are currently registerd in + * the KGame proeprty handler. See KGamePropertyBase::setPolicy + * + * @param p is the new policy for all properties of this handler + * @param userspace if userspace is true (default) only user properties are changed. + * Otherwise the system properties are also changed. + **/ + void setPolicy(KGamePropertyBase::PropertyPolicy p, bool userspace=true); + + /** + * Called by the KGame or KPlayer object or the handler itself to delay + * emmiting of signals. Lockign keeps a counter and unlock is only achieved + * when every lock is canceld by an unlock. + * While this is set signals are quequed and only emmited after this + * is reset. Its deeper meaning is to prevent inconsistencies in a game + * load or network transfer where a emit could access a property not + * yet loaded or transmitted. Calling this by yourself you better know + * what your are doing. + **/ + void lockDirectEmit(); + + /** + * Removes the lock from the emitting of property signals. Corresponds to + * the lockIndirectEmits + **/ + void unlockDirectEmit(); + + /** + * Returns the default policy for this property handler. All properties + * registered newly, will have this property. + **/ + KGamePropertyBase::PropertyPolicy policy(); + + /** + * Calls KGamePropertyBase::setReadOnly(true) for all properties of this + * handler + * + * Use with care! This will even lock the core properties, like name, + * group and myTurn!! + * + * @see unlockProperties + **/ + void lockProperties(); + + /** + * Sends all properties which are marked dirty over the network. This will + * make a forced synchornisation of the properties and mark them all not dirty. + **/ + void flush(); + + /** + * Reference to the internal dictionary + **/ + QIntDict<KGamePropertyBase> &dict() const; + + /** + * In several situations you just want to have a QString of a + * KGameProperty object. This is the case in the + * KGameDebugDialog where the value of all properties is displayed. This + * function will provide you with such a QString for all the types + * used inside of all KGame classes. If you have a non-standard + * property (probably a self defined class or something like this) you + * also need to connect to signalRequestValue to make this function + * useful. + * @param property Return the value of this KGameProperty + * @return The value of a KGameProperty + **/ + QString propertyValue(KGamePropertyBase* property); + + + /** + * Writes some debug output to the console. + **/ + void Debug(); + + +signals: + /** + * This is emitted by a property. KGamePropertyBase::emitSignal + * calls emitSignal which emits this signal. + * + * This signal is emitted whenever the property is changed. Note that + * you can switch off this behaviour using + * KGamePropertyBase::setEmittingSignal in favor of performance. Note + * that you won't experience any performance loss using signals unless + * you use dozens or hundreds of properties which change very often. + **/ + void signalPropertyChanged(KGamePropertyBase *); + + /** + * This signal is emitted when a property needs to be sent. Only the + * parent has to react to this. + * @param msgid The id of the handler + * @param sent set this to true if the property was sent successfully - + * otherwise don't touch + **/ + void signalSendMessage(int msgid, QDataStream &, bool* sent); // AB shall we change bool* into bool& again? + + /** + * If you call propertyValue with a non-standard KGameProperty + * it is possible that the value cannot automatically be converted into a + * QString. Then this signal is emitted and asks you to provide the + * correct value. You probably want to use something like this to achieve + * this: + * \code + * #include <typeinfo> + * void slotRequestValue(KGamePropertyBase* p, QString& value) + * { + * if (*(p->typeinfo()) == typeid(MyType) { + * value = QString(((KGameProperty<MyType>*)p)->value()); + * } + * } + * \endcode + * + * @param property The KGamePropertyBase the value is requested for + * @param value The value of this property. You have to set this. + **/ + void signalRequestValue(KGamePropertyBase* property, QString& value); + +private: + void init(); + +private: + KGamePropertyHandlerPrivate* d; +}; + +#endif diff --git a/libkdegames/kgame/kgamepropertylist.h b/libkdegames/kgame/kgamepropertylist.h new file mode 100644 index 00000000..3a304e70 --- /dev/null +++ b/libkdegames/kgame/kgamepropertylist.h @@ -0,0 +1,258 @@ +/* + 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. +*/ + +#ifndef __KGAMEPROPERTYLIST_H_ +#define __KGAMEPROPERTYLIST_H_ + +#include <qvaluelist.h> + +#include <kdebug.h> + +#include "kgamemessage.h" +#include "kgameproperty.h" +#include "kgamepropertyhandler.h" + +// AB: also see README.LIB! + +template<class type> +class KGamePropertyList : public QValueList<type>, public KGamePropertyBase +{ +public: + /** + * Typedefs + */ + typedef QValueListIterator<type> Iterator; + typedef QValueListConstIterator<type> ConstIterator; + + KGamePropertyList() :QValueList<type>(), KGamePropertyBase() + { + } + + KGamePropertyList( const KGamePropertyList<type> &a ) : QValueList<type>(a) + { + } + + uint findIterator(Iterator me) + { + Iterator it; + uint cnt=0; + for( it = this->begin(); it != this->end(); ++it ) + { + if (me==it) + { + return cnt; + } + cnt++; + } + return this->count(); + } + + Iterator insert( Iterator it, const type& d ) + { + it=QValueList<type>::insert(it,d); + + QByteArray b; + QDataStream s(b, IO_WriteOnly); + KGameMessage::createPropertyCommand(s,KGamePropertyBase::IdCommand,id(),CmdInsert); + int i=findIterator(it); + s << i; + s << d; + if (policy() == PolicyClean || policy() == PolicyDirty) + { + if (mOwner) + { + mOwner->sendProperty(s); + } + } + if (policy() == PolicyDirty || policy() == PolicyLocal) + { + extractProperty(b); + } + return it; + } + + void prepend( const type& d) { insert(this->begin(),d); } + + void append( const type& d ) + { + QByteArray b; + QDataStream s(b, IO_WriteOnly); + KGameMessage::createPropertyCommand(s,KGamePropertyBase::IdCommand,id(),CmdAppend); + s << d; + if (policy() == PolicyClean || policy() == PolicyDirty) + { + if (mOwner) + { + mOwner->sendProperty(s); + } + } + if (policy() == PolicyDirty || policy() == PolicyLocal) + { + extractProperty(b); + } + } + + Iterator erase( Iterator it ) + { + QByteArray b; + QDataStream s(b, IO_WriteOnly); + KGameMessage::createPropertyCommand(s,KGamePropertyBase::IdCommand,id(),CmdRemove); + int i=findIterator(it); + s << i; + if (policy() == PolicyClean || policy() == PolicyDirty) + { + if (mOwner) + { + mOwner->sendProperty(s); + } + } + if (policy() == PolicyDirty || policy() == PolicyLocal) + { + extractProperty(b); + } + //TODO: return value - is it correct for PolicyLocal|PolicyDirty? +// return QValueList<type>::remove(it); + return it; + } + + Iterator remove( Iterator it ) + { + return erase(it); + } + + void remove( const type& d ) + { + Iterator it=find(d); + remove(it); + } + + void clear() + { + QByteArray b; + QDataStream s(b, IO_WriteOnly); + KGameMessage::createPropertyCommand(s,KGamePropertyBase::IdCommand,id(),CmdClear); + if (policy() == PolicyClean || policy() == PolicyDirty) + { + if (mOwner) + { + mOwner->sendProperty(s); + } + } + if (policy() == PolicyDirty || policy() == PolicyLocal) + { + extractProperty(b); + } + } + + void load(QDataStream& s) + { + kdDebug(11001) << "KGamePropertyList load " << id() << endl; + QValueList<type>::clear(); + uint size; + type data; + s >> size; + + for (unsigned int i=0;i<size;i++) + { + s >> data; + QValueList<type>::append(data); + } + if (isEmittingSignal()) emitSignal(); + } + + void save(QDataStream &s) + { + kdDebug(11001) << "KGamePropertyList save "<<id() << endl; + type data; + uint size=this->count(); + s << size; + Iterator it; + for( it = this->begin(); it != this->end(); ++it ) + { + data=*it; + s << data; + } + } + + void command(QDataStream &s,int cmd,bool) + { + KGamePropertyBase::command(s, cmd); + kdDebug(11001) << "---> LIST id="<<id()<<" got command ("<<cmd<<") !!!" <<endl; + Iterator it; + switch(cmd) + { + case CmdInsert: + { + uint i; + type data; + s >> i >> data; + it=this->at(i); + QValueList<type>::insert(it,data); +// kdDebug(11001) << "CmdInsert:id="<<id()<<" i="<<i<<" data="<<data <<endl; + if (isEmittingSignal()) emitSignal(); + break; + } + case CmdAppend: + { + type data; + s >> data; + QValueList<type>::append(data); +// kdDebug(11001) << "CmdAppend:id=" << id() << " data=" << data << endl; + if (isEmittingSignal()) emitSignal(); + break; + } + case CmdRemove: + { + uint i; + s >> i; + it=this->at(i); + QValueList<type>::remove(it); + kdDebug(11001) << "CmdRemove:id="<<id()<<" i="<<i <<endl; + if (isEmittingSignal()) emitSignal(); + break; + } + case CmdClear: + { + QValueList<type>::clear(); + kdDebug(11001) << "CmdClear:id="<<id()<<endl; + if (isEmittingSignal()) emitSignal(); + break; + } + default: + kdDebug(11001) << "Error in KPropertyList::command: Unknown command " << cmd << endl; + } + } + +protected: + void extractProperty(const QByteArray& b) + // this is called for Policy[Dirty|Local] after putting the stuff into the + // stream + { + QDataStream s(b, IO_ReadOnly); + int cmd; + int propId; + KGameMessage::extractPropertyHeader(s, propId); + KGameMessage::extractPropertyCommand(s, propId, cmd); + command(s, cmd, true); + } + +}; + +#endif diff --git a/libkdegames/kgame/kgamesequence.cpp b/libkdegames/kgame/kgamesequence.cpp new file mode 100644 index 00000000..984a9315 --- /dev/null +++ b/libkdegames/kgame/kgamesequence.cpp @@ -0,0 +1,125 @@ +/* + This file is part of the KDE games library + Copyright (C) 2003 Andreas Beckermann ([email protected]) + Copyright (C) 2003 Martin Heni ([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 "kgamesequence.h" +#include "kgamesequence.moc" + +#include "kplayer.h" +#include "kgame.h" + +KGameSequence::KGameSequence() : QObject() +{ + mGame = 0; + mCurrentPlayer = 0; +} + +KGameSequence::~KGameSequence() +{ +} + +void KGameSequence::setGame(KGame* game) +{ + mGame = game; +} + +void KGameSequence::setCurrentPlayer(KPlayer* player) +{ + mCurrentPlayer = player; +} + +KPlayer *KGameSequence::nextPlayer(KPlayer *last,bool exclusive) +{ + kdDebug(11001) << "=================== NEXT PLAYER =========================="<<endl; + if (!game()) + { + kdError() << k_funcinfo << "NULL game object" << endl; + return 0; + } + unsigned int minId,nextId,lastId; + KPlayer *nextplayer, *minplayer; + if (last) + { + lastId = last->id(); + } + else + { + lastId = 0; + } + + kdDebug(11001) << "nextPlayer: lastId="<<lastId<<endl; + + // remove when this has been checked + minId = 0x7fff; // we just need a very large number...properly MAX_UINT or so would be ok... + nextId = minId; + nextplayer = 0; + minplayer = 0; + + KPlayer *player; + for (player = game()->playerList()->first(); player != 0; player=game()->playerList()->next() ) + { + // Find the first player for a cycle + if (player->id() < minId) + { + minId=player->id(); + minplayer=player; + } + if (player==last) + { + continue; + } + // Find the next player which is bigger than the current one + if (player->id() > lastId && player->id() < nextId) + { + nextId=player->id(); + nextplayer=player; + } + } + + // Cycle to the beginning + if (!nextplayer) + { + nextplayer=minplayer; + } + + kdDebug(11001) << k_funcinfo << " ##### lastId=" << lastId << " exclusive=" + << exclusive << " minId=" << minId << " nextid=" << nextId + << " count=" << game()->playerList()->count() << endl; + if (nextplayer) + { + nextplayer->setTurn(true,exclusive); + } + else + { + return 0; + } + return nextplayer; +} + +// Per default we do not do anything +int KGameSequence::checkGameOver(KPlayer*) +{ + return 0; +} +/* + * vim: et sw=2 + */ diff --git a/libkdegames/kgame/kgamesequence.h b/libkdegames/kgame/kgamesequence.h new file mode 100644 index 00000000..4d26ceee --- /dev/null +++ b/libkdegames/kgame/kgamesequence.h @@ -0,0 +1,87 @@ +/* + This file is part of the KDE games library + Copyright (C) 2003 Andreas Beckermann ([email protected]) + Copyright (C) 2003 Martin Heni ([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$ +*/ +#ifndef __KGAMESEQUENCE_H_ +#define __KGAMESEQUENCE_H_ + +#include <qobject.h> + +class KPlayer; +class KGame; + +/** + * This class takes care of round or move management as well of the gameover + * condition. It is especially used for round based games. For these games @ref + * nextPlayer and @ref checkGameOver are the most important methods. + * + * You can subclass KGameSequence and use @ref KGame::setGameSequence to use + * your own rules. Note that @ref KGame will take ownership and therefore will + * delete the object on destruction. + * @short Round/move management class + * @author Andreas Beckermann <[email protected]> + **/ +class KGameSequence : public QObject +{ + Q_OBJECT +public: + KGameSequence(); + virtual ~KGameSequence(); + + /** + * Select the next player in a turn based game. In an asynchronous game this + * function has no meaning. Overwrite this function for your own game sequence. + * Per default it selects the next player in the playerList + */ + virtual KPlayer* nextPlayer(KPlayer *last, bool exclusive = true); + + virtual void setCurrentPlayer(KPlayer* p); + + /** + * @return The @ref KGame object this sequence is for, or NULL if none. + **/ + KGame* game() const { return mGame; } + + KPlayer* currentPlayer() const { return mCurrentPlayer; } + + /** + * Set the @ref KGame object for this sequence. This is called + * automatically by @ref KGame::setGameSequence and you should not call + * it. + **/ + void setGame(KGame* game); + + /** + * Check whether the game is over. The default implementation always + * returns 0. + * + * @param player the player who made the last move + * @return anything else but 0 is considered as game over + **/ + virtual int checkGameOver(KPlayer *player); + +private: + KGame* mGame; + KPlayer* mCurrentPlayer; +}; + +#endif + diff --git a/libkdegames/kgame/kgameversion.h b/libkdegames/kgame/kgameversion.h new file mode 100644 index 00000000..c3147525 --- /dev/null +++ b/libkdegames/kgame/kgameversion.h @@ -0,0 +1,54 @@ +/* + This file is part of the KDE games library + Copyright (C) 2003 Andreas Beckermann ([email protected]) + Copyright (C) 2003 Martin Heni ([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$ +*/ +#ifndef __KGAMEVERSION_H__ +#define __KGAMEVERSION_H__ + +/** + * In this file you find a couple of defines that indicate whether a specific + * feature or function is present in this version of the KGame library. + * + * You don't need this for KDE CVS, but for games that live outside of KDE CVS + * it may be very helpful and a lot easier than writing configure scripts for + * this task. + * + * All defines are prefixed with KGAME_ to avoid conflicts. + **/ + +// KGame::savegame() didn't exist in KDE 3.0 +#define KGAME_HAVE_KGAME_SAVEGAME 1 + +// KGameNetwork::port(), KMessageIO::peerPort() and friends were added in KDE 3.2 +#define KGAME_HAVE_KGAME_PORT 1 + +// KGameNetwork::hostName(), KMessageIO::peerName() and friends were added in KDE 3.2 +#define KGAME_HAVE_KGAME_HOSTNAME 1 + +// KGameSequence class was added in KDE 3.2 +#define KGAME_HAVE_KGAMESEQUENCE 1 + +// KGame::addPlayer() needs to assign an ID to new players, otherwise network is +// broken. this is done in KDE 3.2. +#define KGAME_HAVE_FIXED_ADDPLAYER_ID 1 + +#endif + diff --git a/libkdegames/kgame/kmessageclient.cpp b/libkdegames/kgame/kmessageclient.cpp new file mode 100644 index 00000000..ed9cc966 --- /dev/null +++ b/libkdegames/kgame/kmessageclient.cpp @@ -0,0 +1,373 @@ +/* + This file is part of the KDE games library + Copyright (C) 2001 Burkhard Lehner ([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. +*/ + +#include <kdebug.h> +#include <stdio.h> + +#include <qbuffer.h> +#include <qtimer.h> + +#include "kmessageio.h" +#include "kmessageserver.h" + +#include "kmessageclient.h" + +class KMessageClientPrivate +{ +public: + KMessageClientPrivate () + : adminID (0), connection (0) + {} + + ~KMessageClientPrivate () + { + delete connection; + } + + Q_UINT32 adminID; + QValueList <Q_UINT32> clientList; + KMessageIO *connection; + + bool isLocked; + QValueList <QByteArray> delayedMessages; +}; + +KMessageClient::KMessageClient (QObject *parent, const char *name) + : QObject (parent, name) +{ + d = new KMessageClientPrivate (); + d->isLocked = false; +} + +KMessageClient::~KMessageClient () +{ + d->delayedMessages.clear(); + delete d; +} + +// -- setServer stuff + +void KMessageClient::setServer (const QString &host, Q_UINT16 port) +{ + setServer (new KMessageSocket (host, port)); +} + +void KMessageClient::setServer (KMessageServer *server) +{ + KMessageDirect *serverIO = new KMessageDirect (); + setServer (new KMessageDirect (serverIO)); + server->addClient (serverIO); +} + +void KMessageClient::setServer (KMessageIO *connection) +{ + if (d->connection) + { + delete d->connection; + kdDebug (11001) << k_funcinfo << ": We are changing the server!" << endl; + } + + d->connection = connection; + if (connection ) + { + connect (connection, SIGNAL (received(const QByteArray &)), + this, SLOT (processIncomingMessage(const QByteArray &))); + connect (connection, SIGNAL (connectionBroken()), + this, SLOT (removeBrokenConnection ())); + } +} + +// -- id stuff + +Q_UINT32 KMessageClient::id () const +{ + return (d->connection) ? d->connection->id () : 0; +} + +bool KMessageClient::isAdmin () const +{ + return id() != 0 && id() == adminId(); +} + +Q_UINT32 KMessageClient::adminId () const +{ + return d->adminID; +} + +const QValueList <Q_UINT32> &KMessageClient::clientList() const +{ + return d->clientList; +} + +bool KMessageClient::isConnected () const +{ + return d->connection && d->connection->isConnected(); +} + +bool KMessageClient::isNetwork () const +{ + return isConnected() ? d->connection->isNetwork() : false; +} + +Q_UINT16 KMessageClient::peerPort () const +{ + return d->connection ? d->connection->peerPort() : 0; +} + +QString KMessageClient::peerName () const +{ + return d->connection ? d->connection->peerName() : QString::fromLatin1("localhost"); +} + +// --------------------- Sending messages + +void KMessageClient::sendServerMessage (const QByteArray &msg) +{ + if (!d->connection) + { + kdWarning (11001) << k_funcinfo << ": We have no connection yet!" << endl; + return; + } + d->connection->send (msg); +} + +void KMessageClient::sendBroadcast (const QByteArray &msg) +{ + QByteArray sendBuffer; + QBuffer buffer (sendBuffer); + buffer.open (IO_WriteOnly); + QDataStream stream (&buffer); + + stream << static_cast<Q_UINT32> ( KMessageServer::REQ_BROADCAST ); + buffer.QIODevice::writeBlock (msg); + sendServerMessage (sendBuffer); +} + +void KMessageClient::sendForward (const QByteArray &msg, const QValueList <Q_UINT32> &clients) +{ + QByteArray sendBuffer; + QBuffer buffer (sendBuffer); + buffer.open (IO_WriteOnly); + QDataStream stream (&buffer); + + stream << static_cast<Q_UINT32>( KMessageServer::REQ_FORWARD ) << clients; + buffer.QIODevice::writeBlock (msg); + sendServerMessage (sendBuffer); +} + +void KMessageClient::sendForward (const QByteArray &msg, Q_UINT32 client) +{ + sendForward (msg, QValueList <Q_UINT32> () << client); +} + + +// --------------------- Receiving and processing messages + +void KMessageClient::processIncomingMessage (const QByteArray &msg) +{ + if (d->isLocked) + { + d->delayedMessages.append(msg); + return; + } + if (d->delayedMessages.count() > 0) + { + d->delayedMessages.append (msg); + QByteArray first = d->delayedMessages.front(); + d->delayedMessages.pop_front(); + processMessage (first); + } + else + { + processMessage(msg); + } +} + +void KMessageClient::processMessage (const QByteArray &msg) +{ + if (d->isLocked) + { // must NOT happen, since we check in processIncomingMessage as well as in processFirstMessage + d->delayedMessages.append(msg); + return; + } + QBuffer in_buffer (msg); + in_buffer.open (IO_ReadOnly); + QDataStream in_stream (&in_buffer); + + + bool unknown = false; + + Q_UINT32 messageID; + in_stream >> messageID; + switch (messageID) + { + case KMessageServer::MSG_BROADCAST: + { + Q_UINT32 clientID; + in_stream >> clientID; + emit broadcastReceived (in_buffer.readAll(), clientID); + } + break; + + case KMessageServer::MSG_FORWARD: + { + Q_UINT32 clientID; + QValueList <Q_UINT32> receivers; + in_stream >> clientID >> receivers; + emit forwardReceived (in_buffer.readAll(), clientID, receivers); + } + break; + + case KMessageServer::ANS_CLIENT_ID: + { + bool old_admin = isAdmin(); + Q_UINT32 clientID; + in_stream >> clientID; + d->connection->setId (clientID); + if (old_admin != isAdmin()) + emit adminStatusChanged (isAdmin()); + } + break; + + case KMessageServer::ANS_ADMIN_ID: + { + bool old_admin = isAdmin(); + in_stream >> d->adminID; + if (old_admin != isAdmin()) + emit adminStatusChanged (isAdmin()); + } + break; + + case KMessageServer::ANS_CLIENT_LIST: + { + in_stream >> d->clientList; + } + break; + + case KMessageServer::EVNT_CLIENT_CONNECTED: + { + Q_UINT32 id; + in_stream >> id; + + if (d->clientList.contains (id)) + kdWarning (11001) << k_funcinfo << ": Adding a client that already existed!" << endl; + else + d->clientList.append (id); + + emit eventClientConnected (id); + } + break; + + case KMessageServer::EVNT_CLIENT_DISCONNECTED: + { + Q_UINT32 id; + Q_INT8 broken; + in_stream >> id >> broken; + + if (!d->clientList.contains (id)) + kdWarning (11001) << k_funcinfo << ": Removing a client that doesn't exist!" << endl; + else + d->clientList.remove (id); + + emit eventClientDisconnected (id, bool (broken)); + } + break; + + default: + unknown = true; + } + + if (!unknown && !in_buffer.atEnd()) + kdWarning (11001) << k_funcinfo << ": Extra data received for message ID " << messageID << endl; + + emit serverMessageReceived (msg, unknown); + + if (unknown) + kdWarning (11001) << k_funcinfo << ": received unknown message ID " << messageID << endl; +} + +void KMessageClient::processFirstMessage() +{ + if (d->isLocked) + { + return; + } + if (d->delayedMessages.count() == 0) + { + kdDebug(11001) << k_funcinfo << ": no messages delayed" << endl; + return; + } + QByteArray first = d->delayedMessages.front(); + d->delayedMessages.pop_front(); + processMessage (first); +} + +void KMessageClient::removeBrokenConnection () +{ + kdDebug (11001) << k_funcinfo << ": timer single shot for removeBrokenConnection"<<this << endl; + // MH We cannot directly delete the socket. otherwise QSocket crashes + QTimer::singleShot( 0, this, SLOT(removeBrokenConnection2()) ); + return; +} + + +void KMessageClient::removeBrokenConnection2 () +{ + kdDebug (11001) << k_funcinfo << ": Broken:Deleting the connection object"<<this << endl; + + emit aboutToDisconnect(id()); + delete d->connection; + d->connection = 0; + d->adminID = 0; + emit connectionBroken(); + kdDebug (11001) << k_funcinfo << ": Broken:Deleting the connection object DONE" << endl; +} + +void KMessageClient::disconnect () +{ + kdDebug (11001) << k_funcinfo << ": Disconnect:Deleting the connection object" << endl; + + emit aboutToDisconnect(id()); + delete d->connection; + d->connection = 0; + d->adminID = 0; + emit connectionBroken(); + kdDebug (11001) << k_funcinfo << ": Disconnect:Deleting the connection object DONE" << endl; +} + +void KMessageClient::lock () +{ + d->isLocked = true; +} + +void KMessageClient::unlock () +{ + d->isLocked = false; + for (unsigned int i = 0; i < d->delayedMessages.count(); i++) + { + QTimer::singleShot(0, this, SLOT(processFirstMessage())); + } +} + +unsigned int KMessageClient::delayedMessageCount() const +{ + return d->delayedMessages.count(); +} + +#include "kmessageclient.moc" diff --git a/libkdegames/kgame/kmessageclient.h b/libkdegames/kgame/kmessageclient.h new file mode 100644 index 00000000..90364c8d --- /dev/null +++ b/libkdegames/kgame/kmessageclient.h @@ -0,0 +1,422 @@ +/* + This file is part of the KDE games library + Copyright (C) 2001 Burkhard Lehner ([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. +*/ + +#ifndef __KMESSAGECLIENT_H__ +#define __KMESSAGECLIENT_H__ + +#include <qobject.h> +#include <qstring.h> +#include <qvaluelist.h> + +class KMessageIO; +class KMessageServer; +class KMessageClientPrivate; + +/** + @short A client to connect to a KMessageServer + + This class implements a client that can connect to a KMessageServer object. + It can be used to exchange messages between clients. + + Usually you will connect the signals broadcastReceived and forwardReceived to + some specific slots. In these slot methods you can analyse the messages that are + sent to you from other clients. + + To send messages to other clients, use the methods sendBroadcast() (to send to all + clients) or sendForward() (to send to a list of selected clients). + + If you want to communicate with the KMessageServer object directly (on a more low + level base), use the method sendServerMessage to send a command to the server and + connect to the signal serverMessageReceived to see all the incoming messages. + In that case the messages must be of the format specified in KMessageServer. + @author Burkhard Lehner <[email protected]> +*/ +class KMessageClient : public QObject +{ + Q_OBJECT + +public: + + /** + Constructor. + Creates an unconnected KMessageClient object. Use setServer() later to connect to a + KMessageServer object. + */ + KMessageClient (QObject *parent = 0, const char *name = 0); + + /** + Destructor. + Disconnects from the server, if any connection was established. + */ + ~KMessageClient (); + + /** + @return The client ID of this client. Every client that is connected to a KMessageServer + has a unique ID number. + + NOTE: As long as the object is not yet connected to the server, and as long as the server + hasn't sent the client ID, this method returns 0. + */ + Q_UINT32 id () const; + + /** + @return Whether or not this client is the server admin. + One of the clients connected to the server is the admin and can administrate the server + (set maximum number of clients, remove clients, ...). + + If you use admin commands without being the admin, these commands are simply ignored by + the server. + + NOTE: As long as you are not connected to a server, this method returns false. + */ + bool isAdmin () const; + + /** + @return The ID of the admin client on the message server. + */ + Q_UINT32 adminId() const; + + /** + @return The list of the IDs of all the message clients connected to the message server. + */ + const QValueList <Q_UINT32> &clientList() const; + + /** + Connects the client to (another) server. + + Tries to connect via a TCP/IP socket to a KMessageServer object + on the given host, listening on the specified port. + + If we were already connected, the old connection is closed. + @param host The name of the host to connect to. Must be either a hostname which can + be resolved to an IP or just an IP + @param port The port to connect to + */ + void setServer (const QString &host, Q_UINT16 port); + + /** + Connects the client to (another) server. + + Connects to the given server, using KMessageDirect. + (The server object has to be in the same process.) + + If we were already connected, the old connection is closed. + @param server The KMessageServer to connect to + */ + void setServer (KMessageServer *server); + + /** + * Corresponds to setServer(0); but also emits the connectionBroken signal + **/ + void disconnect(); + + /** + Connects the client to (another) server. + + To use this method, you have to create a KMessageIO object with new (indeed you must + create an instance of a subclass of KMessageIO, e.g. KMessageSocket or KMessageDirect). + This object must already be connected to the new server. + + Calling this method disconnects any earlier connection, and uses the new KMessageIO + object instead. This object gets owned by the KMessageClient object, so don't delete + or manipulate it afterwards. + + With this method it is possible to change the server on the fly. But be careful that + there are no important messages from the old server not yet delivered. + + NOTE: It is very likely that we will have another client ID on the new server. The + value returned by clientID may be a little outdated until the new server tells us + our new ID. + + NOTE: The two other setServer methods are for convenience. If you use them, you don't + have to create a KMessageIO object yourself. + */ + virtual void setServer (KMessageIO *connection); + + /** + @return True, if a connection to a KMessageServer has been started, and if the + connection is ready for transferring data. (It will return false e.g. as long as + a socket connection hasn't been established, and it will also return false after + a socket connection is broken.) + */ + bool isConnected () const; + + /** + @return TRUE if isConnected() is true AND this is not a local (like + KMessageDirect) connection. + */ + bool isNetwork () const; + + /** + @return 0 if isConnected() is FALSE, otherwise the port number this client is + connected to. See also KMessageIO::peerPort and QSocket::peerPort. + @since 3.2 + */ + Q_UINT16 peerPort () const; + + /** + @since 3.2 + @return "localhost" if isConnected() is FALSE, otherwise the hostname this client is + connected to. See also KMessageIO::peerName() and QSocket::peerName(). + */ + QString peerName() const; + + /** + Sends a message to the KMessageServer. If we are not yet connected to one, nothing + happens. + + Use this method to send a low level command to the server. It has to be in the + format specified in KMessageServer. + + If you want to send messages to other clients, you should use sendBroadcast() + and sendForward(). + @param msg The message to be sent to the server. Must be in the format specified in KMessageServer. + */ + void sendServerMessage (const QByteArray &msg); + + /** + Sends a message to all the clients connected to the server, including ourself. + The message consists of an arbitrary block of data with arbitrary length. + + All the clients will receive an exact copy of this block of data, which will be + processed in their processBroadcast() method. + @param msg The message to be sent to the clients + */ + //AB: processBroadcast doesn't exist!! is processIncomingMessage meant? + void sendBroadcast (const QByteArray &msg); + + /** + Sends a message to all the clients in a list. + The message consists of an arbitrary block of data with arbitrary length. + + All clients will receive an exact copy of this block of data, which will be + processed in their processForward() method. + + If the list contains client IDs that are not defined, they are ignored. If + it contains an ID several times, that client will receive the message several + times. + + To send a message to the admin of the KMessageServer, you can use 0 as clientID, + instead of using the real client ID. + @param msg The message to be sent to the clients + @param clients A list of clients the message should be sent to + */ + //AB: processForward doesn't exist!! is processIncomingMessage meant? + void sendForward (const QByteArray &msg, const QValueList <Q_UINT32> &clients); + + /** + Sends a message to a single client. This is a convenieance method. It calls + sendForward (const QByteArray &msg, const QValueList <Q_UINT32> &clients) + with a list containing only one client ID. + + To send a message to the admin of the KMessageServer, you can use 0 as clientID, + instead of using the real client ID. + @param msg The message to be sent to the client + @param client The id of the client the message shall be sent to + */ + void sendForward (const QByteArray &msg, Q_UINT32 client); + + /** + Once this function is called no message will be received anymore. + processIncomingMessage() gets delayed until unlock() is called. + + Note that all messages are still received, but their delivery (like + broadcastReceived()) get delayed only. + */ + void lock(); + + /** + Deliver every message that was delayed by lock() and actually deliver + all messages that get received from now on. + */ + void unlock(); + + /** + @return The number of messages that got delayed since lock() was called + */ + unsigned int delayedMessageCount() const; + +signals: + /** + This signal is emitted when the client receives a broadcast message from the + KMessageServer, sent by another client. Connect to this signal to analyse the + received message and do the right reaction. + + senderID contains the ID of the client that sent the broadcast message. You can + use this e.g. to send a reply message to only that client. Or you can use it + to ignore broadcast messages that were sent by yourself: + + \code + void myObject::myBroadcastSlot (const QByteArray &msg, Q_UINT32 senderID) + { + if (senderID == ((KMessageClient *)sender())->id()) + return; + ... + } + \endcode + @param msg The message that has been sent to us + @param senderID The ID of the client which sent the message + */ + void broadcastReceived (const QByteArray &msg, Q_UINT32 senderID); + + /** + This signal is emitted when the client receives a forward message from the + KMessageServer, sent by another client. Connect to this signal to analyse the + received message and do the right reaction. + + senderID contains the ID of the client that sent the broadcast message. You can + use this e.g. to send a reply message to only that client. + + receivers contains the list of the clients that got the message. (If this list + only contains one number, this will be your client ID, and it was exclusivly + sent to you.) + + If you don't want to distinguish between broadcast and forward messages and + treat them the same, you can connect forwardReceived signal to the + broadcastReceived signal. (Yes, that's possible! You can connect a Qt signal to + a Qt signal, and the second one can have less parameters.) + + \code + KMessageClient *client = new KMessageClient (); + connect (client, SIGNAL (forwardReceived (const QByteArray &, Q_UINT32, const QValueList <Q_UINT32>&)), + client, SIGNAL (broadcastReceived (const QByteArray &, Q_UINT32))); + \endcode + + Then connect the broadcast signal to your slot that analyzes the message. + @param msg The message that has been sent to us + @param senderID The ID of the client which sent the message + @param receivers All clients which receive this message + */ + void forwardReceived (const QByteArray &msg, Q_UINT32 senderID, const QValueList <Q_UINT32> &receivers); + + /** + This signal is emitted when the connection to the KMessageServer is broken. + Reasons for this can be: a network error, a server breakdown, or you were just kicked + from the server. + + When this signal is sent, the connection is already lost and the client is unconnected. + You can connect to another server by calling setServer() afterwards. But keep in mind that + some important messages might have vanished. + */ + void connectionBroken (); + + /** + This signal is emitted right before the client disconnects. It can be used + to this store the id of the client which is about to be lost. + */ + void aboutToDisconnect(Q_UINT32 id); + + /** + This signal is emitted when this client becomes the admin client or when it loses + the admin client status. Connect to this signal if you have to do any initialization + or cleanup. + @param isAdmin Whether we are now admin or not + */ + void adminStatusChanged (bool isAdmin); + + /** + This signal is emitted when another client has connected + to the server. Connect to this method if that clients needs initialization. + This should usually only be done in one client, e.g. the admin client. + @param clientID The ID of the client that has newly connectd. + */ + void eventClientConnected (Q_UINT32 clientID); + + /** + This signal is emitted when the server has lost the + connection to one of the clients (This could be because of a bad internet connection + or because the client disconnected on purpose). + @param clientID The ID of the client that has disconnected + @param broken true if it was disconnected because of a network error + */ + void eventClientDisconnected (Q_UINT32 clientID, bool broken); + + /** + This signal is emitted on every message that came from the server. You can connect to this + signal to see the messages directly. They are in the format specified in KMessageServer. + + @param msg The message that has been sent to us + @param unknown True when KMessageClient didn't recognize the message, i.e. it contained an unknown + message ID. If you want to add additional message types to the client, connect to this signal, + and if unknown is true, analyse the message by yourself. If you recognized the message, + set unknown to false (Otherwise a debug message will be printed). + */ + //AB: maybe add a setNoEmit() so that the other signals can be deactivated? + //Could be a performance benefit (note: KMessageClient is a time critical + //class!!!) + void serverMessageReceived (const QByteArray &msg, bool &unknown); + +protected: + /** + This slot is called from processIncomingMessage or + processFirstMessage, depending on whether the client is locked or a delayed + message is still here (see lock) + + It processes the message and analyses it. If it is a broadcast or a forward message from + another client, it emits the signal processBroadcast or processForward accordingly. + + If you want to treat additional server messages, you can overwrite this method. Don't + forget to call processIncomingMessage of your superclass! + + At the moment, the following server messages are interpreted: + + MSG_BROADCAST, MSG_FORWARD, ANS_CLIENT_ID, ANS_ADMIN_ID, ANS_CLIENT_LIST + @param msg The incoming message + */ + + virtual void processMessage (const QByteArray& msg); + +protected slots: + /** + This slot is called from the signal KMessageIO::received whenever a message from the + KMessageServer arrives. + + It processes the message and analyses it. If it is a broadcast or a forward message from + another client, it emits the signal processBroadcast or processForward accordingly. + + If you want to treat additional server messages, you can overwrite this method. Don't + forget to call processIncomingMessage() of your superclass! + + At the moment, the following server messages are interpreted: + + MSG_BROADCAST, MSG_FORWARD, ANS_CLIENT_ID, ANS_ADMIN_ID, ANS_CLIENT_LIST + @param msg The incoming message + */ + virtual void processIncomingMessage (const QByteArray &msg); + + /** + Called from unlock() (using QTimer::singleShot) until all delayed + messages are delivered. + */ + void processFirstMessage(); + + /** + This slot is called from the signal KMessageIO::connectionBroken. + + It deletes the internal KMessageIO object, and resets the client to default + values. To connect again to another server, use setServer. + */ + virtual void removeBrokenConnection (); + void removeBrokenConnection2 (); + +private: + KMessageClientPrivate *d; +}; + +#endif diff --git a/libkdegames/kgame/kmessageio.cpp b/libkdegames/kgame/kmessageio.cpp new file mode 100644 index 00000000..f3353277 --- /dev/null +++ b/libkdegames/kgame/kmessageio.cpp @@ -0,0 +1,482 @@ +/* + This file is part of the KDE games library + Copyright (C) 2001 Burkhard Lehner ([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. +*/ + +/* + KMessageIO class and subclasses KMessageSocket and KMessageDirect +*/ + +#include "kmessageio.h" +#include <qsocket.h> +#include <kdebug.h> +#include <kprocess.h> +#include <qfile.h> + +// ----------------------- KMessageIO ------------------------- + +KMessageIO::KMessageIO (QObject *parent, const char *name) + : QObject (parent, name), m_id (0) +{} + +KMessageIO::~KMessageIO () +{} + +void KMessageIO::setId (Q_UINT32 id) +{ + m_id = id; +} + +Q_UINT32 KMessageIO::id () +{ + return m_id; +} + +// ----------------------KMessageSocket ----------------------- + +KMessageSocket::KMessageSocket (QString host, Q_UINT16 port, QObject *parent, +const char *name) + : KMessageIO (parent, name) +{ + mSocket = new QSocket (); + mSocket->connectToHost (host, port); + initSocket (); +} + +KMessageSocket::KMessageSocket (QHostAddress host, Q_UINT16 port, QObject +*parent, const char *name) + : KMessageIO (parent, name) +{ + mSocket = new QSocket (); + mSocket->connectToHost (host.toString(), port); + initSocket (); +} + +KMessageSocket::KMessageSocket (QSocket *socket, QObject *parent, const char +*name) + : KMessageIO (parent, name) +{ + mSocket = socket; + initSocket (); +} + +KMessageSocket::KMessageSocket (int socketFD, QObject *parent, const char +*name) + : KMessageIO (parent, name) +{ + mSocket = new QSocket (); + mSocket->setSocket (socketFD); + initSocket (); +} + +KMessageSocket::~KMessageSocket () +{ + delete mSocket; +} + +bool KMessageSocket::isConnected () const +{ + return mSocket->state() == QSocket::Connection; +} + +void KMessageSocket::send (const QByteArray &msg) +{ + QDataStream str (mSocket); + str << Q_UINT8 ('M'); // magic number for begin of message + str.writeBytes (msg.data(), msg.size()); // writes the length (as Q_UINT32) and the data +} + +void KMessageSocket::processNewData () +{ + if (isRecursive) + return; + isRecursive = true; + + QDataStream str (mSocket); + while (mSocket->bytesAvailable() > 0) + { + if (mAwaitingHeader) + { + // Header = magic number + packet length = 5 bytes + if (mSocket->bytesAvailable() < 5) + { + isRecursive = false; + return; + } + + // Read the magic number first. If something unexpected is found, + // start over again, ignoring the data that was read up to then. + + Q_UINT8 v; + str >> v; + if (v != 'M') + { + kdWarning(11001) << k_funcinfo << ": Received unexpected data, magic number wrong!" << endl; + continue; + } + + str >> mNextBlockLength; + mAwaitingHeader = false; + } + else + { + // Data not completely read => wait for more + if (mSocket->bytesAvailable() < (Q_ULONG) mNextBlockLength) + { + isRecursive = false; + return; + } + + QByteArray msg (mNextBlockLength); + str.readRawBytes (msg.data(), mNextBlockLength); + + // send the received message + emit received (msg); + + // Waiting for the header of the next message + mAwaitingHeader = true; + } + } + + isRecursive = false; +} + +void KMessageSocket::initSocket () +{ + connect (mSocket, SIGNAL (error(int)), SIGNAL (connectionBroken())); + connect (mSocket, SIGNAL (connectionClosed()), SIGNAL (connectionBroken())); + connect (mSocket, SIGNAL (readyRead()), SLOT (processNewData())); + mAwaitingHeader = true; + mNextBlockLength = 0; + isRecursive = false; +} + +Q_UINT16 KMessageSocket::peerPort () const +{ + return mSocket->peerPort(); +} + +QString KMessageSocket::peerName () const +{ + return mSocket->peerName(); +} + +// ----------------------KMessageDirect ----------------------- + +KMessageDirect::KMessageDirect (KMessageDirect *partner, QObject *parent, +const char *name) + : KMessageIO (parent, name), mPartner (0) +{ + // 0 as first parameter leaves the object unconnected + if (!partner) + return; + + // Check if the other object is already connected + if (partner && partner->mPartner) + { + kdWarning(11001) << k_funcinfo << ": Object is already connected!" << endl; + return; + } + + // Connect from us to that object + mPartner = partner; + + // Connect the other object to us + partner->mPartner = this; +} + +KMessageDirect::~KMessageDirect () +{ + if (mPartner) + { + mPartner->mPartner = 0; + emit mPartner->connectionBroken(); + } +} + +bool KMessageDirect::isConnected () const +{ + return mPartner != 0; +} + +void KMessageDirect::send (const QByteArray &msg) +{ + if (mPartner) + emit mPartner->received (msg); + else + kdError(11001) << k_funcinfo << ": Not yet connected!" << endl; +} + + +// ----------------------- KMessageProcess --------------------------- + +KMessageProcess::~KMessageProcess() +{ + kdDebug(11001) << "@@@KMessageProcess::Delete process" << endl; + if (mProcess) + { + mProcess->kill(); + delete mProcess; + mProcess=0; + // Remove not send buffers + mQueue.setAutoDelete(true); + mQueue.clear(); + // Maybe todo: delete mSendBuffer + } +} +KMessageProcess::KMessageProcess(QObject *parent, QString file) : KMessageIO(parent,0) +{ + // Start process + kdDebug(11001) << "@@@KMessageProcess::Start process" << endl; + mProcessName=file; + mProcess=new KProcess; + int id=0; + *mProcess << mProcessName << QString("%1").arg(id); + kdDebug(11001) << "@@@KMessageProcess::Init:Id= " << id << endl; + kdDebug(11001) << "@@@KMessgeProcess::Init:Processname: " << mProcessName << endl; + connect(mProcess, SIGNAL(receivedStdout(KProcess *, char *, int )), + this, SLOT(slotReceivedStdout(KProcess *, char * , int ))); + connect(mProcess, SIGNAL(receivedStderr(KProcess *, char *, int )), + this, SLOT(slotReceivedStderr(KProcess *, char * , int ))); + connect(mProcess, SIGNAL(processExited(KProcess *)), + this, SLOT(slotProcessExited(KProcess *))); + connect(mProcess, SIGNAL(wroteStdin(KProcess *)), + this, SLOT(slotWroteStdin(KProcess *))); + mProcess->start(KProcess::NotifyOnExit,KProcess::All); + mSendBuffer=0; + mReceiveCount=0; + mReceiveBuffer.resize(1024); +} +bool KMessageProcess::isConnected() const +{ + kdDebug(11001) << "@@@KMessageProcess::Is conencted" << endl; + if (!mProcess) return false; + return mProcess->isRunning(); +} +void KMessageProcess::send(const QByteArray &msg) +{ + kdDebug(11001) << "@@@KMessageProcess:: SEND("<<msg.size()<<") to process" << endl; + unsigned int size=msg.size()+2*sizeof(long); + + char *tmpbuffer=new char[size]; + long *p1=(long *)tmpbuffer; + long *p2=p1+1; + kdDebug(11001) << "p1="<<p1 << "p2="<< p2 << endl; + memcpy(tmpbuffer+2*sizeof(long),msg.data(),msg.size()); + *p1=0x4242aeae; + *p2=size; + + QByteArray *buffer=new QByteArray(); + buffer->assign(tmpbuffer,size); + // buffer->duplicate(msg); + mQueue.enqueue(buffer); + writeToProcess(); +} +void KMessageProcess::writeToProcess() +{ + // Previous send ok and item in queue + if (mSendBuffer || mQueue.isEmpty()) return ; + mSendBuffer=mQueue.dequeue(); + if (!mSendBuffer) return ; + + // write it out to the process + // kdDebug(11001) << " @@@@@@ writeToProcess::SEND to process " << mSendBuffer->size() << " BYTE " << endl; + // char *p=mSendBuffer->data(); + // for (int i=0;i<16;i++) printf("%02x ",(unsigned char)(*(p+i)));printf("\n"); + mProcess->writeStdin(mSendBuffer->data(),mSendBuffer->size()); + +} +void KMessageProcess::slotWroteStdin(KProcess * ) +{ + kdDebug(11001) << k_funcinfo << endl; + if (mSendBuffer) + { + delete mSendBuffer; + mSendBuffer=0; + } + writeToProcess(); +} + +void KMessageProcess::slotReceivedStderr(KProcess * proc, char *buffer, int buflen) +{ + int pid=0; + int len; + char *p; + char *pos; +// kdDebug(11001)<<"############# Got stderr " << buflen << " bytes" << endl; + + if (!buffer || buflen==0) return ; + if (proc) pid=proc->pid(); + + + pos=buffer; + do + { + p=(char *)memchr(pos,'\n',buflen); + if (!p) len=buflen; + else len=p-pos; + + QByteArray a; + a.setRawData(pos,len); + QString s(a); + kdDebug(11001) << "PID" <<pid<< ":" << s << endl; + a.resetRawData(pos,len); + if (p) pos=p+1; + buflen-=len+1; + }while(buflen>0); +} + + +void KMessageProcess::slotReceivedStdout(KProcess * , char *buffer, int buflen) +{ + kdDebug(11001) << "$$$$$$ " << k_funcinfo << ": Received " << buflen << " bytes over inter process communication" << endl; + + // TODO Make a plausibility check on buflen to avoid memory overflow + while (mReceiveCount+buflen>=mReceiveBuffer.size()) mReceiveBuffer.resize(mReceiveBuffer.size()+1024); + memcpy(mReceiveBuffer.data()+mReceiveCount,buffer,buflen); + mReceiveCount+=buflen; + + // Possbile message + while (mReceiveCount>2*sizeof(long)) + { + long *p1=(long *)mReceiveBuffer.data(); + long *p2=p1+1; + unsigned int len; + if (*p1!=0x4242aeae) + { + kdDebug(11001) << k_funcinfo << ": Cookie error...transmission failure...serious problem..." << endl; +// for (int i=0;i<mReceiveCount;i++) fprintf(stderr,"%02x ",mReceiveBuffer[i]);fprintf(stderr,"\n"); + } + len=(int)(*p2); + if (len<2*sizeof(long)) + { + kdDebug(11001) << k_funcinfo << ": Message size error" << endl; + break; + } + if (len<=mReceiveCount) + { + kdDebug(11001) << k_funcinfo << ": Got message with len " << len << endl; + + QByteArray msg; + // msg.setRawData(mReceiveBuffer.data()+2*sizeof(long),len-2*sizeof(long)); + msg.duplicate(mReceiveBuffer.data()+2*sizeof(long),len-2*sizeof(long)); + emit received(msg); + // msg.resetRawData(mReceiveBuffer.data()+2*sizeof(long),len-2*sizeof(long)); + // Shift buffer + if (len<mReceiveCount) + { + memmove(mReceiveBuffer.data(),mReceiveBuffer.data()+len,mReceiveCount-len); + } + mReceiveCount-=len; + } + else break; + } +} + +void KMessageProcess::slotProcessExited(KProcess * /*p*/) +{ + kdDebug(11001) << "Process exited (slot)" << endl; + emit connectionBroken(); + delete mProcess; + mProcess=0; +} + + +// ----------------------- KMessageFilePipe --------------------------- +KMessageFilePipe::KMessageFilePipe(QObject *parent,QFile *readfile,QFile *writefile) : KMessageIO(parent,0) +{ + mReadFile=readfile; + mWriteFile=writefile; + mReceiveCount=0; + mReceiveBuffer.resize(1024); +} + +KMessageFilePipe::~KMessageFilePipe() +{ +} + +bool KMessageFilePipe::isConnected () const +{ + return (mReadFile!=0)&&(mWriteFile!=0); +} + +void KMessageFilePipe::send(const QByteArray &msg) +{ + unsigned int size=msg.size()+2*sizeof(long); + + char *tmpbuffer=new char[size]; + long *p1=(long *)tmpbuffer; + long *p2=p1+1; + memcpy(tmpbuffer+2*sizeof(long),msg.data(),msg.size()); + *p1=0x4242aeae; + *p2=size; + + QByteArray buffer; + buffer.assign(tmpbuffer,size); + mWriteFile->writeBlock(buffer); + mWriteFile->flush(); + /* + fprintf(stderr,"+++ KMessageFilePipe:: SEND(%d to parent) realsize=%d\n",msg.size(),buffer.size()); + for (int i=0;i<buffer.size();i++) fprintf(stderr,"%02x ",buffer[i]);fprintf(stderr,"\n"); + fflush(stderr); + */ +} + +void KMessageFilePipe::exec() +{ + + // According to BL: Blocking read is ok + // while(mReadFile->atEnd()) { usleep(100); } + + int ch=mReadFile->getch(); + + while (mReceiveCount>=mReceiveBuffer.size()) mReceiveBuffer.resize(mReceiveBuffer.size()+1024); + mReceiveBuffer[mReceiveCount]=(char)ch; + mReceiveCount++; + + // Change for message + if (mReceiveCount>=2*sizeof(long)) + { + long *p1=(long *)mReceiveBuffer.data(); + long *p2=p1+1; + unsigned int len; + if (*p1!=0x4242aeae) + { + fprintf(stderr,"KMessageFilePipe::exec:: Cookie error...transmission failure...serious problem...\n"); +// for (int i=0;i<16;i++) fprintf(stderr,"%02x ",mReceiveBuffer[i]);fprintf(stderr,"\n"); + } + len=(int)(*p2); + if (len==mReceiveCount) + { + //fprintf(stderr,"KMessageFilePipe::exec:: Got Message with len %d\n",len); + + QByteArray msg; + //msg.setRawData(mReceiveBuffer.data()+2*sizeof(long),len-2*sizeof(long)); + msg.duplicate(mReceiveBuffer.data()+2*sizeof(long),len-2*sizeof(long)); + emit received(msg); + //msg.resetRawData(mReceiveBuffer.data()+2*sizeof(long),len-2*sizeof(long)); + mReceiveCount=0; + } + } + + + return ; + + +} + +#include "kmessageio.moc" diff --git a/libkdegames/kgame/kmessageio.h b/libkdegames/kgame/kmessageio.h new file mode 100644 index 00000000..37cf35cd --- /dev/null +++ b/libkdegames/kgame/kmessageio.h @@ -0,0 +1,416 @@ +/* + This file is part of the KDE games library + Copyright (C) 2001 Burkhard Lehner ([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. +*/ + +/* + KMessageIO class and subclasses KMessageSocket and KMessageDirect +*/ + +#ifndef _KMESSAGEIO_H_ +#define _KMESSAGEIO_H_ + +#include <qcstring.h> +#include <qhostaddress.h> +#include <qobject.h> +#include <qstring.h> +#include <qptrqueue.h> +#include <qfile.h> +#include <kdebug.h> + +class QSocket; +class KProcess; +//class QFile; + + +/** + This abstract base class represents one end of a message connections + between two clients. Each client has one object of a subclass of + KMessageIO. Calling /e send() on one object will emit the signal + /e received() on the other object, and vice versa. + + For each type of connection (TCP/IP socket, COM port, direct connection + within the same class) a subclass of KMessageIO must be defined that + implements the pure virtual methods /e send() and /e isConnected(), + and sends the signals. (See /e KMessageSocket for an example implementation.) + + Two subclasses are already included: /e KMessageSocket (connection using + TCP/IP sockets) and /e KMessageDirect (connection using method calls, both + sides must be within the same process). +*/ + +class KMessageIO : public QObject +{ + Q_OBJECT + +public: + /** + * The usual QObject constructor, does nothing else. + **/ + KMessageIO (QObject *parent = 0, const char *name = 0); + + /** + * The usual destructor, does nothing special. + **/ + ~KMessageIO (); + + /** + * The runtime idendifcation + */ + virtual int rtti() const {return 0;} + + /** + * @return Whether this KMessageIO is a network IO or not. + **/ + //virtual bool isNetwork () const = 0; + virtual bool isNetwork () const + { + kdError(11001) << "Calling PURE virtual isNetwork...BAD" << endl; + return false; + } + + /** + This method returns the status of the object, whether it is already + (or still) connected to another KMessageIO object or not. + + This is a pure virtual method, so it has to be implemented in a subclass + of KMessageIO. + */ + //virtual bool isConnected () const = 0; + virtual bool isConnected () const + { + kdError(11001) << "Calling PURE virtual isConencted...BAD" << endl; + return false; + } + + /** + Sets the ID number of this object. This number can for example be used to + distinguish several objects in a server. + + NOTE: Sometimes it is useful to let two connected KMessageIO objects + have the same ID number. You have to do so yourself, KMessageIO doesn't + change this value on its own! + */ + void setId (Q_UINT32 id); + + /** + Queries the ID of this object. + */ + Q_UINT32 id (); + + /** + @since 3.2 + @return 0 in the default implementation. Reimplemented in @ref KMessageSocket. + */ + virtual Q_UINT16 peerPort () const { return 0; } + + /** + @since 3.2 + @return "localhost" in the default implementation. Reimplemented in @ref KMessageSocket + */ + virtual QString peerName () const { return QString::fromLatin1("localhost"); } + + +signals: + /** + This signal is emitted when /e send() on the connected KMessageIO + object is called. The parameter contains the same data array in /e msg + as was used in /e send(). + */ + void received (const QByteArray &msg); + + /** + This signal is emitted when the connection is closed. This can be caused + by a hardware error (e.g. broken internet connection) or because the other + object was killed. + + Note: Sometimes a broken connection can be undetected for a long time, + or may never be detected at all. So don't rely on this signal! + */ + void connectionBroken (); + +public slots: + + /** + This slot sends the data block in /e msg to the connected object, that will + emit /e received(). + + For a concrete class, you have to subclass /e KMessageIO and overwrite this + method. In the subclass, implement this method as an ordinary method, not + as a slot! (Otherwise another slot would be defined. It would work, but uses + more memory and time.) See /e KMessageSocket for an example implementation. + */ + virtual void send (const QByteArray &msg) = 0; + +protected: + Q_UINT32 m_id; +}; + + +/** + This class implements the message communication using a TCP/IP socket. The + object can connect to a server socket, or can use an already connected socket. +*/ + +class KMessageSocket : public KMessageIO +{ + Q_OBJECT + +public: + /** + Connects to a server socket on /e host with /e port. host can be an + numerical (e.g. "192.168.0.212") or symbolic (e.g. "wave.peter.org") + IP address. You can immediately use the /e sendSystem() and + /e sendBroadcast() methods. The messages are stored and sent to the + receiver after the connection is established. + + If the connection could not be established (e.g. unknown host or no server + socket at this port), the signal /e connectionBroken is emitted. + */ + KMessageSocket (QString host, Q_UINT16 port, QObject *parent = 0, + const char *name = 0); + + /** + Connects to a server socket on /e host with /e port. You can immediately + use the /e sendSystem() and /e sendBroadcast() methods. The messages are + stored and sent to the receiver after the connection is established. + + If the connection could not be established (e.g. unknown host or no server + socket at this port), the signal /e connectionBroken is emitted. + */ + KMessageSocket (QHostAddress host, Q_UINT16 port, QObject *parent = 0, + const char *name = 0); + + /** + Uses /e socket to do the communication. + + The socket should already be connected, or at least be in /e connecting + state. + + Note: The /e socket object is then owned by the /e KMessageSocket object. + So don't use it otherwise any more and don't delete it. It is deleted + together with this KMessageSocket object. (Use 0 as parent for the QSocket + object t ensure it is not deleted.) + */ + KMessageSocket (QSocket *socket, QObject *parent = 0, const char *name = 0); + + /** + Uses the socket specified by the socket descriptor socketFD to do the + communication. The socket must already be connected. + + This constructor can be used with a QServerSocket within the (pure + virtual) method /e newConnection. + + Note: The socket is then owned by the /e KMessageSocket object. So don't + manipulate the socket afterwards, especially don't close it. The socket is + automatically closed when KMessageSocket is deleted. + */ + KMessageSocket (int socketFD, QObject *parent = 0, const char *name = 0); + + /** + Destructor, closes the socket. + */ + ~KMessageSocket (); + + /** + * The runtime idendifcation + */ + virtual int rtti() const {return 1;} + + /** + @since 3.2 + @return The port that this object is connected to. See QSocket::peerPort + */ + virtual Q_UINT16 peerPort () const; + + /** + @since 3.2 + @return The hostname this object is connected to. See QSocket::peerName. + */ + virtual QString peerName () const; + + /** + @return TRUE as this is a network IO. + */ + bool isNetwork() const { return true; } + + /** + Returns true if the socket is in state /e connected. + */ + bool isConnected () const; + + /** + Overwritten slot method from KMessageIO. + + Note: It is not declared as a slot method, since the slot is already + defined in KMessageIO as a virtual method. + */ + void send (const QByteArray &msg); + +protected slots: + virtual void processNewData (); + +protected: + void initSocket (); + QSocket *mSocket; + bool mAwaitingHeader; + Q_UINT32 mNextBlockLength; + + bool isRecursive; // workaround for "bug" in QSocket, Qt 2.2.3 or older +}; + + +/** + This class implements the message communication using function calls + directly. It can only be used when both sides of the message pipe are + within the same process. The communication is very fast. + + To establish a communication, you have to create two instances of + KMessageDirect, the first one with no parameters in the constructor, + the second one with the first as parameter: + + /code + KMessageDirect *peer1, *peer2; + peer1 = new KMessageDirect (); // unconnected + peer2 = new KMessageDirect (peer1); // connect with peer1 + /endcode + + The connection is only closed when one of the instances is deleted. +*/ + +class KMessageDirect : public KMessageIO +{ + Q_OBJECT + +public: + /** + Creates an object and connects it to the object given in the first + parameter. Use 0 as first parameter to create an unconnected object, + that is later connected. + + If that object is already connected, the object remains unconnected. + */ + KMessageDirect (KMessageDirect *partner = 0, QObject *parent = 0, const char +*name = 0); + + /** + Destructor, closes the connection. + */ + ~KMessageDirect (); + + /** + * The runtime idendifcation + */ + virtual int rtti() const {return 2;} + + + /** + @return FALSE as this is no network IO. + */ + bool isNetwork() const { return false; } + + /** + Returns true, if the object is connected to another instance. + + If you use the first constructor, the object is unconnected unless another + object is created with this one as parameter. + + The connection can only be closed by deleting one of the objects. + */ + bool isConnected () const; + + /** + Overwritten slot method from KMessageIO. + + Note: It is not declared as a slot method, since the slot is already + defined in KMessageIO as a virtual method. + */ + void send (const QByteArray &msg); + +protected: + KMessageDirect *mPartner; +}; + +class KMessageProcess : public KMessageIO +{ + Q_OBJECT + + public: + KMessageProcess(QObject *parent, QString file); + ~KMessageProcess(); + bool isConnected() const; + void send (const QByteArray &msg); + void writeToProcess(); + + /** + @return FALSE as this is no network IO. + */ + bool isNetwork() const { return false; } + + /** + * The runtime idendifcation + */ + virtual int rtti() const {return 3;} + + + + public slots: + void slotReceivedStdout(KProcess *proc, char *buffer, int buflen); + void slotReceivedStderr(KProcess *proc, char *buffer, int buflen); + void slotProcessExited(KProcess *p); + void slotWroteStdin(KProcess *p); + + private: + QString mProcessName; + KProcess *mProcess; + QPtrQueue <QByteArray> mQueue; + QByteArray *mSendBuffer; + QByteArray mReceiveBuffer; + unsigned int mReceiveCount; +}; + +class KMessageFilePipe : public KMessageIO +{ + Q_OBJECT + + public: + KMessageFilePipe(QObject *parent,QFile *readFile,QFile *writeFile); + ~KMessageFilePipe(); + bool isConnected() const; + void send (const QByteArray &msg); + void exec(); + + /** + @return FALSE as this is no network IO. + */ + bool isNetwork() const { return false; } + + /** + * The runtime idendifcation + */ + virtual int rtti() const {return 4;} + + + + private: + QFile *mReadFile; + QFile *mWriteFile; + QByteArray mReceiveBuffer; + unsigned int mReceiveCount; +}; + +#endif diff --git a/libkdegames/kgame/kmessageserver.cpp b/libkdegames/kgame/kmessageserver.cpp new file mode 100644 index 00000000..e857ea31 --- /dev/null +++ b/libkdegames/kgame/kmessageserver.cpp @@ -0,0 +1,515 @@ +/* + This file is part of the KDE games library + Copyright (C) 2001 Burkhard Lehner ([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. +*/ + +#include <qiodevice.h> +#include <qbuffer.h> +#include <qptrlist.h> +#include <qptrqueue.h> +#include <qtimer.h> +#include <qvaluelist.h> + +#include <kdebug.h> + +#include "kmessageio.h" +#include "kmessageserver.h" + +// --------------- internal class KMessageServerSocket + +KMessageServerSocket::KMessageServerSocket (Q_UINT16 port, QObject *parent) + : QServerSocket (port, 0, parent) +{ +} + +KMessageServerSocket::~KMessageServerSocket () +{ +} + +void KMessageServerSocket::newConnection (int socket) +{ + emit newClientConnected (new KMessageSocket (socket)); +} + +// ---------------- class for storing an incoming message + +class MessageBuffer +{ + public: + MessageBuffer (Q_UINT32 clientID, const QByteArray &messageData) + : id (clientID), data (messageData) { } + ~MessageBuffer () {} + Q_UINT32 id; + QByteArray data; +}; + +// ---------------- KMessageServer's private class + +class KMessageServerPrivate +{ +public: + KMessageServerPrivate() + : mMaxClients (-1), mGameId (1), mUniqueClientNumber (1), mAdminID (0), mServerSocket (0) + { + mClientList.setAutoDelete (true); + mMessageQueue.setAutoDelete (true); + } + + int mMaxClients; + int mGameId; + Q_UINT16 mCookie; + Q_UINT32 mUniqueClientNumber; + Q_UINT32 mAdminID; + + KMessageServerSocket* mServerSocket; + + QPtrList <KMessageIO> mClientList; + QPtrQueue <MessageBuffer> mMessageQueue; + QTimer mTimer; + bool mIsRecursive; +}; + + +// ------------------ KMessageServer + +KMessageServer::KMessageServer (Q_UINT16 cookie,QObject* parent) + : QObject(parent, 0) +{ + d = new KMessageServerPrivate; + d->mIsRecursive=false; + d->mCookie=cookie; + connect (&(d->mTimer), SIGNAL (timeout()), + this, SLOT (processOneMessage())); + kdDebug(11001) << "CREATE(KMessageServer=" + << this + << ") cookie=" + << d->mCookie + << " sizeof(this)=" + << sizeof(KMessageServer) + << endl; +} + +KMessageServer::~KMessageServer() +{ + kdDebug(11001) << k_funcinfo << "this=" << this << endl; + Debug(); + stopNetwork(); + deleteClients(); + delete d; + kdDebug(11001) << k_funcinfo << " done" << endl; +} + +//------------------------------------- TCP/IP server stuff + +bool KMessageServer::initNetwork (Q_UINT16 port) +{ + kdDebug(11001) << k_funcinfo << endl; + + if (d->mServerSocket) + { + kdDebug (11001) << k_funcinfo << ": We were already offering connections!" << endl; + delete d->mServerSocket; + } + + d->mServerSocket = new KMessageServerSocket (port); + d->mIsRecursive = false; + + if (!d->mServerSocket || !d->mServerSocket->ok()) + { + kdError(11001) << k_funcinfo << ": Serversocket::ok() == false" << endl; + delete d->mServerSocket; + d->mServerSocket=0; + return false; + } + + kdDebug (11001) << k_funcinfo << ": Now listening to port " + << d->mServerSocket->port() << endl; + connect (d->mServerSocket, SIGNAL (newClientConnected (KMessageIO*)), + this, SLOT (addClient (KMessageIO*))); + return true; +} + +Q_UINT16 KMessageServer::serverPort () const +{ + if (d->mServerSocket) + return d->mServerSocket->port(); + else + return 0; +} + +void KMessageServer::stopNetwork() +{ + if (d->mServerSocket) + { + delete d->mServerSocket; + d->mServerSocket = 0; + } +} + +bool KMessageServer::isOfferingConnections() const +{ + return d->mServerSocket != 0; +} + +//----------------------------------------------- adding / removing clients + +void KMessageServer::addClient (KMessageIO* client) +{ + QByteArray msg; + + // maximum number of clients reached? + if (d->mMaxClients >= 0 && d->mMaxClients <= clientCount()) + { + kdError (11001) << k_funcinfo << ": Maximum number of clients reached!" << endl; + return; + } + + // give it a unique ID + client->setId (uniqueClientNumber()); + kdDebug (11001) << k_funcinfo << ": " << client->id() << endl; + + // connect its signals + connect (client, SIGNAL (connectionBroken()), + this, SLOT (removeBrokenClient())); + connect (client, SIGNAL (received (const QByteArray &)), + this, SLOT (getReceivedMessage (const QByteArray &))); + + // Tell everyone about the new guest + // Note: The new client doesn't get this message! + QDataStream (msg, IO_WriteOnly) << Q_UINT32 (EVNT_CLIENT_CONNECTED) << client->id(); + broadcastMessage (msg); + + // add to our list + d->mClientList.append (client); + + // tell it its ID + QDataStream (msg, IO_WriteOnly) << Q_UINT32 (ANS_CLIENT_ID) << client->id(); + client->send (msg); + + // Give it the complete list of client IDs + QDataStream (msg, IO_WriteOnly) << Q_UINT32 (ANS_CLIENT_LIST) << clientIDs(); + client->send (msg); + + + if (clientCount() == 1) + { + // if it is the first client, it becomes the admin + setAdmin (client->id()); + } + else + { + // otherwise tell it who is the admin + QDataStream (msg, IO_WriteOnly) << Q_UINT32 (ANS_ADMIN_ID) << adminID(); + client->send (msg); + } + + emit clientConnected (client); +} + +void KMessageServer::removeClient (KMessageIO* client, bool broken) +{ + Q_UINT32 clientID = client->id(); + if (!d->mClientList.removeRef (client)) + { + kdError(11001) << k_funcinfo << ": Deleting client that wasn't added before!" << endl; + return; + } + + // tell everyone about the removed client + QByteArray msg; + QDataStream (msg, IO_WriteOnly) << Q_UINT32 (EVNT_CLIENT_DISCONNECTED) << client->id() << (Q_INT8)broken; + broadcastMessage (msg); + + // If it was the admin, select a new admin. + if (clientID == adminID()) + { + if (!d->mClientList.isEmpty()) + setAdmin (d->mClientList.first()->id()); + else + setAdmin (0); + } +} + +void KMessageServer::deleteClients() +{ + d->mClientList.clear(); + d->mAdminID = 0; +} + +void KMessageServer::removeBrokenClient () +{ + if (!sender()->inherits ("KMessageIO")) + { + kdError (11001) << k_funcinfo << ": sender of the signal was not a KMessageIO object!" << endl; + return; + } + + KMessageIO *client = (KMessageIO *) sender(); + + emit connectionLost (client); + removeClient (client, true); +} + + +void KMessageServer::setMaxClients(int c) +{ + d->mMaxClients = c; +} + +int KMessageServer::maxClients() const +{ + return d->mMaxClients; +} + +int KMessageServer::clientCount() const +{ + return d->mClientList.count(); +} + +QValueList <Q_UINT32> KMessageServer::clientIDs () const +{ + QValueList <Q_UINT32> list; + for (QPtrListIterator <KMessageIO> iter (d->mClientList); *iter; ++iter) + list.append ((*iter)->id()); + return list; +} + +KMessageIO* KMessageServer::findClient (Q_UINT32 no) const +{ + if (no == 0) + no = d->mAdminID; + + QPtrListIterator <KMessageIO> iter (d->mClientList); + while (*iter) + { + if ((*iter)->id() == no) + return (*iter); + ++iter; + } + return 0; +} + +Q_UINT32 KMessageServer::adminID () const +{ + return d->mAdminID; +} + +void KMessageServer::setAdmin (Q_UINT32 adminID) +{ + // Trying to set the the client that is already admin => nothing to do + if (adminID == d->mAdminID) + return; + + if (adminID > 0 && findClient (adminID) == 0) + { + kdWarning (11001) << "Trying to set a new admin that doesn't exist!" << endl; + return; + } + + d->mAdminID = adminID; + + QByteArray msg; + QDataStream (msg, IO_WriteOnly) << Q_UINT32 (ANS_ADMIN_ID) << adminID; + + // Tell everyone about the new master + broadcastMessage (msg); +} + + +//------------------------------------------- ID stuff + +Q_UINT32 KMessageServer::uniqueClientNumber() const +{ + return d->mUniqueClientNumber++; +} + +// --------------------- Messages --------------------------- + +void KMessageServer::broadcastMessage (const QByteArray &msg) +{ + for (QPtrListIterator <KMessageIO> iter (d->mClientList); *iter; ++iter) + (*iter)->send (msg); +} + +void KMessageServer::sendMessage (Q_UINT32 id, const QByteArray &msg) +{ + KMessageIO *client = findClient (id); + if (client) + client->send (msg); +} + +void KMessageServer::sendMessage (const QValueList <Q_UINT32> &ids, const QByteArray &msg) +{ + for (QValueListConstIterator <Q_UINT32> iter = ids.begin(); iter != ids.end(); ++iter) + sendMessage (*iter, msg); +} + +void KMessageServer::getReceivedMessage (const QByteArray &msg) +{ + if (!sender() || !sender()->inherits("KMessageIO")) + { + kdError (11001) << k_funcinfo << ": slot was not called from KMessageIO!" << endl; + return; + } + //kdDebug(11001) << k_funcinfo << ": size=" << msg.size() << endl; + KMessageIO *client = (KMessageIO *) sender(); + Q_UINT32 clientID = client->id(); + + //QByteArray *ta=new QByteArray; + //ta->duplicate(msg); + //d->mMessageQueue.enqueue (new MessageBuffer (clientID, *ta)); + + + d->mMessageQueue.enqueue (new MessageBuffer (clientID, msg)); + if (!d->mTimer.isActive()) + d->mTimer.start(0); // AB: should be , TRUE i guess +} + +void KMessageServer::processOneMessage () +{ + // This shouldn't happen, since the timer should be stopped before. But only to be sure! + if (d->mMessageQueue.isEmpty()) + { + d->mTimer.stop(); + return; + } + if (d->mIsRecursive) + { + return; + } + d->mIsRecursive = true; + + MessageBuffer *msg_buf = d->mMessageQueue.head(); + + Q_UINT32 clientID = msg_buf->id; + QBuffer in_buffer (msg_buf->data); + in_buffer.open (IO_ReadOnly); + QDataStream in_stream (&in_buffer); + + QByteArray out_msg; + QBuffer out_buffer (out_msg); + out_buffer.open (IO_WriteOnly); + QDataStream out_stream (&out_buffer); + + bool unknown = false; + + QByteArray ttt=in_buffer.buffer(); + Q_UINT32 messageID; + in_stream >> messageID; + //kdDebug(11001) << k_funcinfo << ": got message with messageID=" << messageID << endl; + switch (messageID) + { + case REQ_BROADCAST: + out_stream << Q_UINT32 (MSG_BROADCAST) << clientID; + // FIXME, compiler bug? + // this should be okay, since QBuffer is subclass of QIODevice! : + // out_buffer.writeBlock (in_buffer.readAll()); + out_buffer.QIODevice::writeBlock (in_buffer.readAll()); + broadcastMessage (out_msg); + break; + + case REQ_FORWARD: + { + QValueList <Q_UINT32> clients; + in_stream >> clients; + out_stream << Q_UINT32 (MSG_FORWARD) << clientID << clients; + // see above! + out_buffer.QIODevice::writeBlock (in_buffer.readAll()); + sendMessage (clients, out_msg); + } + break; + + case REQ_CLIENT_ID: + out_stream << Q_UINT32 (ANS_CLIENT_ID) << clientID; + sendMessage (clientID, out_msg); + break; + + case REQ_ADMIN_ID: + out_stream << Q_UINT32 (ANS_ADMIN_ID) << d->mAdminID; + sendMessage (clientID, out_msg); + break; + + case REQ_ADMIN_CHANGE: + if (clientID == d->mAdminID) + { + Q_UINT32 newAdmin; + in_stream >> newAdmin; + setAdmin (newAdmin); + } + break; + + case REQ_REMOVE_CLIENT: + if (clientID == d->mAdminID) + { + QValueList <Q_UINT32> client_list; + in_stream >> client_list; + for (QValueListIterator <Q_UINT32> iter = client_list.begin(); iter != client_list.end(); ++iter) + { + KMessageIO *client = findClient (*iter); + if (client) + removeClient (client, false); + else + kdWarning (11001) << k_funcinfo << ": removing non-existing clientID" << endl; + } + } + break; + + case REQ_MAX_NUM_CLIENTS: + if (clientID == d->mAdminID) + { + Q_INT32 maximum_clients; + in_stream >> maximum_clients; + setMaxClients (maximum_clients); + } + break; + + case REQ_CLIENT_LIST: + { + out_stream << Q_UINT32 (ANS_CLIENT_LIST) << clientIDs(); + sendMessage (clientID, out_msg); + } + break; + + default: + unknown = true; + } + + // check if all the data has been used + if (!unknown && !in_buffer.atEnd()) + kdWarning (11001) << k_funcinfo << ": Extra data received for message ID " << messageID << endl; + + emit messageReceived (msg_buf->data, clientID, unknown); + + if (unknown) + kdWarning (11001) << k_funcinfo << ": received unknown message ID " << messageID << endl; + + // remove the message, since we are ready with it + d->mMessageQueue.remove(); + if (d->mMessageQueue.isEmpty()) + d->mTimer.stop(); + d->mIsRecursive = false; +} + +void KMessageServer::Debug() +{ + kdDebug(11001) << "------------------ KMESSAGESERVER -----------------------" << endl; + kdDebug(11001) << "MaxClients : " << maxClients() << endl; + kdDebug(11001) << "NoOfClients : " << clientCount() << endl; + kdDebug(11001) << "---------------------------------------------------" << endl; +} + +#include "kmessageserver.moc" diff --git a/libkdegames/kgame/kmessageserver.h b/libkdegames/kgame/kmessageserver.h new file mode 100644 index 00000000..0049a2e7 --- /dev/null +++ b/libkdegames/kgame/kmessageserver.h @@ -0,0 +1,492 @@ +/* + This file is part of the KDE games library + Copyright (C) 2001 Burkhard Lehner ([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. +*/ + +#ifndef __KMESSAGESERVER_H__ +#define __KMESSAGESERVER_H__ + +#include <qobject.h> +#include <qserversocket.h> +#include <qstring.h> +#include <qvaluelist.h> + +class KMessageIO; +class KMessageServerPrivate; + +/** + @short A server for message sending and broadcasting, using TCP/IP connections. + + An object of this class listens for incoming connections via TCP/IP sockets and + creates KMessageSocket objects for every established connection. It receives + messages from the "clients", analyses them and processes an appropriate + reaction. + + You can also use other KMessageIO objects with KMessageServer, not only + TCP/IP socket based ones. Use addClient to connect via an object of any + KMessageIO subclass. (For clients within the same process, you can e.g. use + KMessageDirect.) This object already has to be connected. + + The messages are always packages of an arbitrary length. The format of the messages + is given below. All the data is stored and received with QDataStream, to be + platform independant. + + Setting up a KMessageServer can be done like this: + + \code + KMessageServer *server = new KMessageServer (); + server->initNetwork (TCP/IP-Portnumber); + \endcode + + Usually that is everything you will do. There are a lot of public methods to + administrate the object (maximum number of clients, finding clients, removing + clients, setting the admin client, ...), but this functionality can also + be done by messages from the clients. So you can administrate the object completely + on remote. + + If you want to extend the Server for your own needs (e.g. additional message types), + you can either create a subclass and overwrite the method processOneMessage. + (But don't forget to call the method of the superclass!) Or you can connect to + the signal messageReceived, and analyse the messages there. + + Every client has a unique ID, so that messages can be sent to another dedicated + client or a list of clients. + + One of the clients (the admin) has a special administration right. Some of the + administration messages can only be used with him. The admin can give the admin + status to another client. You can send a message to the admin by using clientID 0. + This is always interpreted as the admin client, independant of its real clientID. + + Here is a list of the messages the KMessageServer understands: + << means, the value is inserted into the QByteArray using QDataStream. The + messageIDs (REQ_BROADCAST, ...) are of type Q_UINT32. + + - QByteArray << static_cast<Q_UINT32>( REQ_BROADCAST ) << raw_data + + When the server receives this message, it sends the following message to + ALL connected clients (a broadcast), where the raw_data is left unchanged: + QByteArray << static_cast <Q_UINT32>( MSG_BROADCAST ) << clientID << raw_data + Q_UINT32 clientID; // the ID of the client that sent the broadcast request + + - QByteArray << static_cast<Q_UINT32>( REQ_FORWARD ) << client_list << raw_data + QValueList <Q_UINT32> client_list; // list of receivers + + When the server receives this message, it sends the following message to + the clients in client_list: + QByteArray << static_cast<Q_UINT32>( MSG_FORWARD ) << senderID << client_list << raw_data + Q_UINT32 senderID; // the sender of the forward request + QValueList <Q_UINT32> client_list; // a copy of the receiver list + + Note: Every client receives the message as many times as he is in the client_list. + Note: Since the client_list is sent to all the clients, every client can see who else + got the message. If you want to prevent this, send a single REQ_FORWARD + message for every receiver. + + - QByteArray << static_cast<Q_UINT32>( REQ_CLIENT_ID ) + + When the server receives this message, it sends the following message to + the asking client: + QByteArray << static_cast<Q_UINT32>( ANS_CLIENT_ID ) << clientID + Q_UINT32 clientID; // The ID of the client who asked for it + + Note: This answer is also automatically sent to a new connected client, so that he + can store his ID. The ID of a client doesn't change during his lifetime, and is + unique for this KMessageServer. + + - QByteArray << static_cast<Q_UINT32>( REQ_ADMIN_ID ) + + When the server receives this message, it sends the following message to + the asking client: + QByteArray << ANS_ADMIN_ID << adminID + Q_UINT32 adminID; // The ID of the admin + + Note: This answer is also automatically sent to a new connected client, so that he + can see if he is the admin or not. It will also be sent to all connected clients + when a new admin is set (see REQ_ADMIN_CHANGE). + + - QByteArray << static_cast<Q_UINT32>( REQ_ADMIN_CHANGE ) << new_admin + Q_UINT32 new_admin; // the ID of the new admin, or 0 for no admin + + When the server receives this message, it sets the admin to the new ID. If no client + with that ID exists, nothing happens. With new_admin == 0 no client is a admin. + ONLY THE ADMIN ITSELF CAN USE THIS MESSAGE! + + Note: The server sends a ANS_ADMIN_ID message to every connected client. + + - QByteArray << static_cast<Q_UINT32>( REQ_REMOVE_CLIENT ) << client_list + QValueList <Q_UINT32> client_list; // The list of clients to be removed + + When the server receives this message, it removes the clients with the ids stored in + client_list, disconnecting the connection to them. + ONLY THE ADMIN CAN USE THIS MESSAGE! + + Note: If one of the clients is the admin himself, he will also be deleted. + Another client (if any left) will become the new admin. + + - QByteArray << static_cast<Q_UINT32>( REQ_MAX_NUM_CLIENTS ) << maximum_clients + Q_INT32 maximum_clients; // The maximum of clients connected, or infinite if -1 + + When the server receives this message, it limits the number of clients to the number given, + or sets it unlimited for maximum_clients == -1. + ONLY THE ADMIN CAN USE THIS MESSAGE! + + Note: If there are already more clients, they are not affected. It only prevents new Clients + to be added. To assure this limit, remove clients afterwards (REQ_REMOVE_CLIENT) + + - QByteArray << static_cast<Q_UINT32>( REQ_CLIENT_LIST ) + + When the server receives this message, it answers by sending a list of IDs of all the clients + that are connected at the moment. So it sends the following message to the asking client: + QByteArray << static_cast<Q_UINT32>( ANS_CLIENT_LIST ) << clientList + QValueList <Q_UINT32> clientList; // The IDs of the connected clients + + Note: This message is also sent to every new connected client, so that he knows the other + clients. + + There are two more messages that are sent from the server to the every client automatically + when a new client connects or a connection to a client is lost: + + QByteArray << static_cast<Q_UINT32>( EVNT_CLIENT_CONNECTED ) << clientID; + Q_UINT32 clientID; // the ID of the new connected client + + QByteArray << static_cast<Q_UINT32>( EVNT_CLIENT_DISCONNECTED ) << clientID; + Q_UINT32 clientID; // the ID of the client that lost the connection + Q_UINT8 broken; // 1 if the network connection was closed, 0 if it was disconnected + // on purpose + + + @author Andreas Beckermann <[email protected]>, Burkhard Lehner <[email protected]> + @version $Id$ +*/ +class KMessageServer : public QObject +{ + Q_OBJECT + +public: + /** + MessageIDs for messages from a client to the message server. + */ + enum { + REQ_BROADCAST = 1, + REQ_FORWARD, + REQ_CLIENT_ID, + REQ_ADMIN_ID, + REQ_ADMIN_CHANGE, + REQ_REMOVE_CLIENT, + REQ_MAX_NUM_CLIENTS, + REQ_CLIENT_LIST, + REQ_MAX_REQ = 0xffff }; + + /** + * MessageIDs for messages from the message server to a client. + **/ + enum { + MSG_BROADCAST = 101, + MSG_FORWARD, + ANS_CLIENT_ID, + ANS_ADMIN_ID, + ANS_CLIENT_LIST, + EVNT_CLIENT_CONNECTED, + EVNT_CLIENT_DISCONNECTED, + EVNT_MAX_EVNT = 0xffff + }; + + /** + * Create a KGameNetwork object + **/ + KMessageServer(Q_UINT16 cookie = 42, QObject* parent = 0); + + ~KMessageServer(); + + /** + * Gives debug output of the game status + **/ + virtual void Debug(); + +//---------------------------------- TCP/IP server stuff + + /** + * Starts the Communication server to listen for incoming TCP/IP connections. + * + * @param port The port on which the service is offered, or 0 to let the + * system pick a free port + * @return true if it worked + */ + bool initNetwork (Q_UINT16 port = 0); + + /** + * Returns the TCP/IP port number we are listening to for incoming connections. + * (This has to be known by other clients so that they can connect to us. It's + * especially necessary if you used 0 as port number in initNetwork(). + * @return the port number + **/ + Q_UINT16 serverPort () const; + + /** + * Stops listening for connections. The already running connections are + * not affected. + * To listen for connections again call initNetwork again. + **/ + void stopNetwork(); + + /** + * Are we still offer offering server connections? + * @return true, if we are still listening to connections requests + **/ + bool isOfferingConnections() const; + +//---------------------------------- adding / removing clients + +public slots: + /** + * Adds a new @ref KMessageIO object to the communication server. This "client" + * gets a unique ID. + * + * This slot method is automatically called for any incoming TCP/IP + * connection. You can use it to add other types of connections, e.g. + * local connections (KMessageDirect) to the server manually. + * + * NOTE: The @ref KMessageIO object gets owned by the KMessageServer, + * so don't delete or manipulate it afterwards. It is automatically deleted + * when the connection is broken or the communication server is deleted. + * So, add a @ref KMessageIO object to just ONE KMessageServer. + **/ + void addClient (KMessageIO *); + + /** + * Removes the KMessageIO object from the client list and deletes it. + * This destroys the connection, if it already was up. + * Does NOT emit connectionLost. + * Sends an info message to the other clients, that contains the ID of + * the removed client and the value of the parameter broken. + * + * @param io the object to delete and to remove from the client list + * @param broken true if the client has lost connection + * Mostly used internally. You will probably not need this. + **/ + void removeClient (KMessageIO *io, bool broken); + + /** + Deletes all connections to the clients. + */ + void deleteClients(); + +private slots: + /** + * Removes the sender object of the signal that called this slot. It is + * automatically connected to @ref KMessageIO::connectionBroken. + * Emits @ref connectionLost (KMessageIO*), and deletes the @ref KMessageIO object. + * Don't call it directly! + **/ + void removeBrokenClient (); + +public: + /** + * sets the maximum number of clients which can connect. + * If this number is reached, no more clients can be added. + * Setting this number to -1 means unlimited number of clients. + * + * NOTE: Existing connections are not affected. + * So, clientCount > maxClients is possible, if there were already + * more clients than allowed before reducing this value. + * + * @param maxnumber the number of clients + **/ + void setMaxClients(int maxnumber); + + /** + * returns the maximum number of clients + * + * @return the number of clients + **/ + int maxClients() const; + + /** + * returns the current number of connected clients. + * + * @return the number of clients + **/ + int clientCount() const; + + /** + * returns a list of the unique IDs of all clients. + **/ + QValueList <Q_UINT32> clientIDs() const; + + /** + * Find the @ref KMessageIO object to the given client number. + * @param no the client number to look for, or 0 to look for the admin + * @return address of the client, or 0 if no client with that number exists + **/ + KMessageIO *findClient (Q_UINT32 no) const; + + /** + * Returns the clientID of the admin, if there is a admin, 0 otherwise. + * + * NOTE: Most often you don't need to know that id, since you can + * use clientID 0 to specify the admin. + **/ + Q_UINT32 adminID() const; + + /** + * Sets the admin to a new client with the given ID. + * The old admin (if existed) and the new admin will get the ANS_ADMIN message. + * If you use 0 as new adminID, no client will be admin. + **/ + void setAdmin (Q_UINT32 adminID); + + +//------------------------------ ID stuff + + /* + * The unique ID of this game + * + * @return int id + **/ +// int gameId() const; + + /* + * Application cookie. this idendifies the game application. It + * help to distinguish between e.g. KPoker and KWin4 + * + * @return the application cookie + **/ +// int cookie() const; + +//------------------------------ Message stuff + +public: + /** + * Sends a message to all connected clients. + * The message is NOT translated in any way. This method calls + * @ref KMessageIO::send for every client added. + **/ + virtual void broadcastMessage (const QByteArray &msg); + + /** + * Sends a message to a single client with the given ID. + * The message is NOT translated in any way. + * If no client with the given id exists, nothing is done. + * This is just a convenience method. You could also call + * @ref findClient (id)->send(msg) manually, but this method checks for + * errors. + **/ + virtual void sendMessage (Q_UINT32 id, const QByteArray &msg); + + /** + * Sends a message to a list of clients. Their ID is given in ids. If + * a client id is given more than once in the list, the message is also + * sent several times to that client. + * This is just a convenience method. You could also iterate over the + * list of IDs. + **/ + virtual void sendMessage (const QValueList <Q_UINT32> &ids, const QByteArray &msg); + +protected slots: + /** + * This slot receives all the messages from the @ref KMessageIO::received signals. + * It stores the messages in a queue. The messages are later taken out of the queue + * by @ref getReceivedMessage. + * + * NOTE: It is important that this slot may only be called from the signal + * @ref KMessageIO::received, since the sender() object is used to find out + * the client that sent the message! + **/ + virtual void getReceivedMessage (const QByteArray &msg); + + /** + * This slot is called whenever there are elements in the message queue. This queue + * is filled by @ref getReceivedMessage. + * This slot takes one message out of the queue and analyses processes it, + * if it recognizes it. (See message types in the description of the class.) + * After that, the signal @ref messageReceived is emitted. Connect to that signal if + * you want to process other types of messages. + **/ + virtual void processOneMessage (); + +//---------------------------- Signals + +signals: + /** + * A new client connected to the game + * @param client the client object that connected + **/ + void clientConnected (KMessageIO *client); + + /** + * A network connection got broken. Note that the client will automatically get deleted + * after this signal is emitted. The signal is not emitted when the client was removed + * regularly. + * + * @param client the client which left the game + **/ + void connectionLost (KMessageIO *client); + + /** + * This signal is always emitted when a message from a client is received. + * + * You can use this signal to extend the communication server without subclassing. + * Just connect to this signal and analyse the message, if unknown is true. + * If you recognize a message and process it, set unknown to false, otherwise + * a warning message is printed. + * + * @param data the message data + * @param clientID the ID of the KMessageIO object that received the message + * @param unknown true, if the message type is not known by the KMessageServer + **/ + void messageReceived (const QByteArray &data, Q_UINT32 clientID, bool &unknown); + +protected: + /** + * @return A unique number which can be used as the id of a @ref KMessageIO. It is + * incremented after every call so if you need the id twice you have to save + * it anywhere. It's currently used to initialize newly connected clints only. + **/ + Q_UINT32 uniqueClientNumber() const; + +private: + KMessageServerPrivate* d; +}; + + +/** + Internal class of KMessageServer. Creates a server socket and waits for + connections. + + NOTE: This has to be here in the header file, because it is a subclass from + QObject and has to go through the moc. + + @short An internal class for KServerSocket + @author Burkhard Lehner <[email protected]> +*/ +class KMessageServerSocket : public QServerSocket +{ + Q_OBJECT + +public: + KMessageServerSocket (Q_UINT16 port, QObject *parent = 0); + ~KMessageServerSocket (); + + void newConnection (int socket); + +signals: + void newClientConnected (KMessageIO *client); +}; + + + +#endif diff --git a/libkdegames/kgame/kmessageserver.png b/libkdegames/kgame/kmessageserver.png Binary files differnew file mode 100644 index 00000000..07fba6c5 --- /dev/null +++ b/libkdegames/kgame/kmessageserver.png diff --git a/libkdegames/kgame/kplayer.cpp b/libkdegames/kgame/kplayer.cpp new file mode 100644 index 00000000..0f8ea184 --- /dev/null +++ b/libkdegames/kgame/kplayer.cpp @@ -0,0 +1,446 @@ +/* + 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 "kgameio.h" +#include "kplayer.h" +#include "kgamemessage.h" +#include "kgamepropertyhandler.h" + +#include <kdebug.h> +#include <klocale.h> + +#include <qbuffer.h> + +#include <stdio.h> +#include <assert.h> + +#define KPLAYER_LOAD_COOKIE 7285 + +class KPlayerPrivate +{ +public: + KPlayerPrivate() + { + mNetworkPlayer = 0; + } + + Q_UINT32 mId; + bool mVirtual; // virtual player + int mPriority; // tag for replacement + + KPlayer* mNetworkPlayer; // replacement player + + KGamePropertyHandler mProperties; + +// Playerdata + KGamePropertyQString mName; + KGamePropertyQString mGroup; +}; + +KPlayer::KPlayer() : QObject(0,0) +{ + init(); +} + +KPlayer::KPlayer(KGame* game) : QObject(0, 0) +{ + init(); + game->addPlayer(this); +} + +void KPlayer::init() +{ +// note that NO KGame object exists here! so we cannot use KGameProperty::send! + kdDebug(11001) << k_funcinfo << ": this=" << this << ", sizeof(this)="<<sizeof(KPlayer) << endl; + kdDebug(11001) << "sizeof(m_Group)="<<sizeof(d->mGroup)<<endl; + d = new KPlayerPrivate; + + d->mProperties.registerHandler(KGameMessage::IdPlayerProperty, + this,SLOT(sendProperty(int, QDataStream&, bool*)), + SLOT(emitSignal(KGamePropertyBase *))); + d->mVirtual=false; + mActive=true; + mGame=0; + d->mId=0; // "0" is always an invalid ID! + d->mPriority=0; + // I guess we cannot translate the group otherwise no + // international conenctions are possible + + mUserId.registerData(KGamePropertyBase::IdUserId, this, i18n("UserId")); + mUserId.setLocal(0); + d->mGroup.registerData(KGamePropertyBase::IdGroup, this, i18n("Group")); + d->mGroup.setLocal(i18n("default")); + d->mName.registerData(KGamePropertyBase::IdName, this, i18n("Name")); + d->mName.setLocal(i18n("default")); + + mAsyncInput.registerData(KGamePropertyBase::IdAsyncInput, this, i18n("AsyncInput")); + mAsyncInput.setLocal(false); + mMyTurn.registerData(KGamePropertyBase::IdTurn, this, i18n("myTurn")); + mMyTurn.setLocal(false); + mMyTurn.setEmittingSignal(true); + mMyTurn.setOptimized(false); +} + +KPlayer::~KPlayer() +{ + kdDebug(11001) << k_funcinfo << ": this=" << this <<", id=" << this->id() << endl; + + // Delete IODevices + KGameIO *input; + while((input=mInputList.first())) + { + delete input; + } + if (game()) + { + game()->playerDeleted(this); + } + +// note: mProperties does not use autoDelete or so - user must delete objects +// himself + d->mProperties.clear(); + delete d; +// kdDebug(11001) << k_funcinfo << " done" << endl; +} + +bool KPlayer::forwardMessage(QDataStream &msg,int msgid,Q_UINT32 receiver,Q_UINT32 sender) +{ + if (!isActive()) + { + return false; + } + if (!game()) + { + return false; + } + kdDebug(11001) << k_funcinfo << ": to game sender="<<sender<<"" << "recv="<<receiver <<"msgid="<<msgid << endl; + return game()->sendSystemMessage(msg,msgid,receiver,sender); +} + +bool KPlayer::forwardInput(QDataStream &msg,bool transmit,Q_UINT32 sender) +{ + if (!isActive()) + { + return false; + } + if (!game()) + { + return false; + } + + kdDebug(11001) << k_funcinfo << ": to game playerInput(sender="<<sender<<")" << endl; + if (!asyncInput() && !myTurn()) + { + kdDebug(11001) << k_funcinfo << ": rejected cause it is not our turn" << endl; + return false; + } + + // AB: I hope I remember the usage correctly: + // this function is called twice (on sender side) - once with transmit = true + // where it sends the input to the comserver and once with transmit = false + // where it really looks at the input + if (transmit) + { + kdDebug(11001) << "indirect playerInput" << endl; + return game()->sendPlayerInput(msg,this,sender); + } + else + { + kdDebug(11001) << "direct playerInput" << endl; + return game()->systemPlayerInput(msg,this,sender); + } +} + +void KPlayer::setId(Q_UINT32 newid) +{ + // Needs to be after the sendProcess + d->mId=newid; +} + + +void KPlayer::setGroup(const QString& group) +{ d->mGroup = group; } + +const QString& KPlayer::group() const +{ return d->mGroup.value(); } + +void KPlayer::setName(const QString& name) +{ d->mName = name; } + +const QString& KPlayer::name() const +{ return d->mName.value(); } + +Q_UINT32 KPlayer::id() const +{ return d->mId; } + +KGamePropertyHandler * KPlayer::dataHandler() +{ return &d->mProperties; } + +void KPlayer::setVirtual(bool v) +{ d->mVirtual = v; } + +bool KPlayer::isVirtual() const +{ return d->mVirtual;} + +void KPlayer::setNetworkPlayer(KPlayer* p) +{ d->mNetworkPlayer = p; } + +KPlayer* KPlayer::networkPlayer() const +{ return d->mNetworkPlayer; } + +int KPlayer::networkPriority() const +{ return d->mPriority; } + +void KPlayer::setNetworkPriority(int p) +{ d->mPriority = p; } + +bool KPlayer::addGameIO(KGameIO *input) +{ + if (!input) + { + return false; + } + mInputList.append(input); + input->initIO(this); // set player and init device + return true; +} + +// input=0, remove all +bool KPlayer::removeGameIO(KGameIO *targetinput,bool deleteit) +{ + kdDebug(11001) << k_funcinfo << ": " << targetinput << " delete=" << deleteit<< endl; + bool result=true; + if (!targetinput) // delete all + { + KGameIO *input; + while((input=mInputList.first())) + { + if (input) removeGameIO(input,deleteit); + } + } + else + { +// kdDebug(11001) << "remove IO " << targetinput << endl; + if (deleteit) + { + delete targetinput; + } + else + { + targetinput->setPlayer(0); + result=mInputList.remove(targetinput); + } + } + return result; +} + +KGameIO * KPlayer::findRttiIO(int rtti) const +{ + QPtrListIterator<KGameIO> it(mInputList); + while (it.current()) + { + if (it.current()->rtti() == rtti) + { + return it.current(); + } + ++it; + } + return 0; +} + +int KPlayer::calcIOValue() +{ + int value=0; + QPtrListIterator<KGameIO> it(mInputList); + while (it.current()) + { + value|=it.current()->rtti(); + ++it; + } + return value; +} + +bool KPlayer::setTurn(bool b, bool exclusive) +{ + kdDebug(11001) << k_funcinfo << ": " << id() << " (" << this << ") to " << b << endl; + if (!isActive()) + { + return false; + } + + // if we get to do an exclusive turn all other players are disallowed + if (exclusive && b && game()) + { + KPlayer *player; + KGame::KGamePlayerList *list=game()->playerList(); + for ( player=list->first(); player != 0; player=list->next() ) + { + if (player==this) + { + continue; + } + player->setTurn(false,false); + } + } + + // Return if nothing changed + mMyTurn = b; + + return true; +} + +bool KPlayer::load(QDataStream &stream) +{ + Q_INT32 id,priority; + stream >> id >> priority; + setId(id); + setNetworkPriority(priority); + + // Load Player Data + //FIXME: maybe set all properties setEmitSignal(false) before? + d->mProperties.load(stream); + + Q_INT16 cookie; + stream >> cookie; + if (cookie==KPLAYER_LOAD_COOKIE) + { + kdDebug(11001) << " Player loaded propertly"<<endl; + } + else + { + kdError(11001) << " Player loading error. probably format error"<<endl; + } + + // emit signalLoad(stream); + return true; +} + +bool KPlayer::save(QDataStream &stream) +{ + stream << (Q_INT32)id() << (Q_INT32)networkPriority(); + + d->mProperties.save(stream); + + stream << (Q_INT16)KPLAYER_LOAD_COOKIE; + + //emit signalSave(stream); + return true; +} + + +void KPlayer::networkTransmission(QDataStream &stream,int msgid,Q_UINT32 sender) +{ + //kdDebug(11001) << k_funcinfo ": msgid=" << msgid << " sender=" << sender << " we are=" << id() << endl; + // PlayerProperties processed + bool issender; + if (game()) + { + issender=sender==game()->gameId(); + } + else + { + issender=true; + } + if (d->mProperties.processMessage(stream,msgid,issender)) + { + return ; + } + switch(msgid) + { + case KGameMessage::IdPlayerInput: + { + kdDebug(11001) << k_funcinfo << ": Got player move " + << "KPlayer (virtual) forwards it to the game object" << endl; + forwardInput(stream,false); + } + break; + default: + emit signalNetworkData(msgid - KGameMessage::IdUser, + ((QBuffer*)stream.device())->readAll(),sender,this); + kdDebug(11001) << k_funcinfo << ": " + << "User data msgid " << msgid << endl; + break; + } + +} + +KGamePropertyBase* KPlayer::findProperty(int id) const +{ + return d->mProperties.find(id); +} + +bool KPlayer::addProperty(KGamePropertyBase* data) +{ + return d->mProperties.addProperty(data); +} + +void KPlayer::sendProperty(int msgid, QDataStream& stream, bool* sent) +{ + if (game()) + { + bool s = game()->sendPlayerProperty(msgid, stream, id()); + if (s) + { + *sent = true; + } + } +} + +void KPlayer::emitSignal(KGamePropertyBase *me) +{ + // Notify KGameIO (Process) for a new turn + if (me->id()==KGamePropertyBase::IdTurn) + { + //kdDebug(11001) << k_funcinfo << ": for KGamePropertyBase::IdTurn " << endl; + QPtrListIterator<KGameIO> it(mInputList); + while (it.current()) + { + it.current()->notifyTurn(mMyTurn.value()); + ++it; + } + } + emit signalPropertyChanged(me,this); +} + +// --------------------- DEBUG -------------------- +void KPlayer::Debug() +{ + kdDebug(11001) << "------------------- KPLAYER -----------------------" << endl; + kdDebug(11001) << "this: " << this << endl; + kdDebug(11001) << "rtti: " << rtti() << endl; + kdDebug(11001) << "id : " << id() << endl; + kdDebug(11001) << "Name : " << name() << endl; + kdDebug(11001) << "Group: " << group() << endl; + kdDebug(11001) << "Async: " << asyncInput() << endl; + kdDebug(11001) << "myTurn: " << myTurn() << endl; + kdDebug(11001) << "Virtual: " << isVirtual() << endl; + kdDebug(11001) << "Active: " << isActive() << endl; + kdDebug(11001) << "Priority:" << networkPriority() << endl; + kdDebug(11001) << "Game : " << game() << endl; + kdDebug(11001) << "#IOs: " << mInputList.count() << endl; + kdDebug(11001) << "---------------------------------------------------" << endl; +} + +#include "kplayer.moc" diff --git a/libkdegames/kgame/kplayer.h b/libkdegames/kgame/kplayer.h new file mode 100644 index 00000000..0e511ac3 --- /dev/null +++ b/libkdegames/kgame/kplayer.h @@ -0,0 +1,471 @@ +/* + 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. +*/ + +#ifndef __KPLAYER_H_ +#define __KPLAYER_H_ + +#include <qstring.h> +#include <qobject.h> +#include <qptrlist.h> + +#include "kgameproperty.h" +#include <kdemacros.h> + +class KGame; +class KGameIO; +class KGamePropertyBase; +class KGamePropertyHandler; + +class KPlayerPrivate; + +/** + * @short Base class for a game player + * + * The KPlayer class is the central player object. It holds + * information about the player and is responsible for any + * input the player does. For this arbitrary many KGameIO + * modules can be plugged into it. Main features are: + * - Handling of IO devices + * - load/save (mostly handled by KGamePropertyHandler) + * - Turn handling (turn based, asynchronous) + * + * A KPlayer depends on a KGame object. Call KGame::addPlayer() to plug + * a KPlayer into a KGame object. Note that you cannot do much with a + * KPlayer object before it has been plugged into a KGame. This is because + * most properties of KPlayer are KGameProperty which need to send messages + * through a KGame object to be changed. + * + * A KGameIO represents the input methods of a player and you should make all + * player inputs through it. So call something like playerInput->move(4); + * instead which should call KGameIO::sendInput() to actually move. This way + * you gain a *very* big advantage: you can exchange a KGameIO whenever you + * want! You can e.g. remove the KGameIO of a local (human) player and just + * replace it by a computerIO on the fly! So from that point on all playerInputs + * are done by the computerIO instead of the human player. You also can replace + * all network players by computer players when the network connection is broken + * or a player wants to quit. + * So remember: use KGameIO whenever possible! A KPlayer should just + * contain all data of the player (KGameIO must not!) and several common + * functions which are shared by all of your KGameIOs. + * + */ +class KDE_EXPORT KPlayer : public QObject +{ + Q_OBJECT + +public: + typedef QPtrList<KGameIO> KGameIOList; + + // KPlayer(KGame *,KGameIO * input=0); + /** + * Create a new player object. It will be automatically + * deleted if the game it belongs to is deleted. + */ + KPlayer(); + + /** + * Create a new player object. It will be automatically + * deleted if the game it belongs to is deleted. This constructor + * automatically adds the player to the game using KGame::addPlayer() + */ + KPlayer(KGame* game); + + virtual ~KPlayer(); + + /** + * The idendification of the player. Overwrite this in + * classes inherting KPlayer to run time identify them. + * + * @return 0 for default KPlayer. + */ + virtual int rtti() const {return 0;} + + /** + * Gives debug output of the game status + */ + void Debug(); + + // properties + /** + * Returns a list of input devices + * + * @return list of devices + */ + KGameIOList *ioList() {return &mInputList;} + + /** + * sets the game the player belongs to. This + * is usually automatically done when adding a + * player + * + * @param game the game + */ + void setGame(KGame *game) {mGame=game;} + + /** + * Query to which game the player belongs to + * + * @return the game + */ + KGame *game() const {return mGame;} + + /** + * Set whether this player can make turns/input + * all the time (true) or only when it is its + * turn (false) as it is used in turn based games + * + * @param a async=true turn based=false + */ + void setAsyncInput(bool a) {mAsyncInput = a;} + + /** + * Query whether this player does asynchronous + * input + * + * @return true/false + */ + bool asyncInput() const {return mAsyncInput.value();} + + /** + * Is this player a virtual player, ie is it + * created by mirroring a real player from another + * network game. This mirroring is done autmatically + * as soon as a network connection is build and it affects + * all players regardless what type + * + * @return true/false + */ + bool isVirtual() const; + + /** + * @internal + * Sets whether this player is virtual. This is internally + * called + * + * @param v virtual true/false + */ + void setVirtual(bool v); + + /** + * Is this player an active player. An player is usually + * inactivated if it is replaced by a network connection. + * But this could also be called manually + * + * @return true/false + */ + bool isActive() const {return mActive;} + + /** + * Set an player as active (true) or inactive (false) + * + * @param v true=active, false=inactive + */ + void setActive(bool v) {mActive=v;} + + /** + * Returns the id of the player + * + * @return the player id + */ + Q_UINT32 id() const; + + /* Set the players id. This is done automatically by + * the game object when adding a new player! + * + * @param i the player id + */ + void setId(Q_UINT32 i); + + /** + * Returns the user defined id of the player + * This value can be used arbitrary by you to + * have some user idendification for your player, + * e.g. 0 for a white chess player, 1 for a black + * one. This value is more reliable than the player + * id whcih can even change when you make a network + * connection. + * + * @return the user defined player id + */ + int userId() const {return mUserId.value();} + + /* Set the user defined players id. + * + * @param i the user defined player id + */ + void setUserId(int i) {mUserId = i;} + + /** + * Returns whether this player can be replaced by a network + * connection player. The name of this function can be + * improved ;-) If you do not overwrite the function to + * select what players shall play in a network the KGame + * does an automatic selection based on the networkPriority + * This is not a terrible important function at the moment. + * + * @return true/false + */ + int networkPriority() const; + + /** + * Set whether this player can be replaced by a network + * player. There are to possible games. The first type + * of game has arbitrary many players. As soon as a network + * players connects the game runs with more players (not tagged + * situation). The other type is e.g. games like chess which + * require a constant player number. In a network game situation + * you would tag one or both players of all participants. As + * soon as the connect the tagged player will then be replaced + * by the network partner and it is then controlled over the network. + * On connection loss the old situation is automatically restored. + * + * The name of this function can be improved;-) + * + * @param b should this player be tagged + */ + void setNetworkPriority(int b); + + /** + * Returns the player which got inactivated to allow + * this player to be set up via network. Mostly internal + * function + */ + KPlayer *networkPlayer() const; + + /** + * Sets this network player replacement. Internal stuff + */ + void setNetworkPlayer(KPlayer *p); + + // A name and group the player belongs to + /** + * A group the player belongs to. This + * Can be set arbitrary by you. + */ + void setGroup(const QString& group); + + /** + * Query the group the player belongs to. + */ + virtual const QString& group() const; + + /** + * Sets the name of the player. + * This can be chosen arbitrary. + * @param name The player's name + */ + void setName(const QString& name); + + /** + * @return The name of the player. + */ + virtual const QString& name() const; + + + // set devices + /** + * Adds an IO device for the player. Possible KGameIO devices + * can either be taken from the existing ones or be self written. + * Existing are e.g. Keyboard, Mouse, Computerplayer + * + * @param input the inut device + * @return true if ok + */ + bool addGameIO(KGameIO *input); + + /** + * remove (and delete) a game IO device + * + * The remove IO(s) is/are deleted by default. If + * you do not want this set the parameter deleteit to false + * + * @param input the device to be removed or 0 for all devices + * @param deleteit true (default) to delete the device otherwisse just remove it + * @return true on ok + */ + bool removeGameIO(KGameIO *input=0,bool deleteit=true); + + /** + * Finds the KGameIO devies with the given rtti code. + * E.g. find the mouse or network device + * + * @param rtti the rtti code to be searched for + * @return the KGameIO device + */ + KGameIO *findRttiIO(int rtti) const; + + /** + * Checks whether this player has a IO device of the + * given rtti type + * + * @param rtti the rtti typed to be checked for + * @return true if it exists + */ + bool hasRtti(int rtti) const {return findRttiIO(rtti)!=0;} + + // Message exchange + /** + * Forwards input to the game object..internal use only + * + * This method is used by KGameIO::sendInput(). Use that function + * instead to send player inputs! + * + * This function forwards a player input (see KGameIO classes) to the + * game object, see KGame, either to KGame::sendPlayerInput() (if + * transmit=true, ie the message has just been created) or to + * KGame::playerInput() (if player=false, ie the message *was* sent through + * KGame::sendPlayerInput). + */ + virtual bool forwardInput(QDataStream &msg,bool transmit=true, Q_UINT32 sender=0); + + /** + * Forwards Message to the game object..internal use only + */ + virtual bool forwardMessage(QDataStream &msg,int msgid,Q_UINT32 receiver=0,Q_UINT32 sender=0); + + // Game logic + /** + * is it my turn to go + * + * @return true/false + */ + bool myTurn() const {return mMyTurn.value();} + + /** + * Sets whether this player is the next to turn. + * If exclusive is given all other players are set + * to setTurn(false) and only this player can move + * + * @param b true/false + * @param exclusive true (default)/ false + * @return should be void + */ + bool setTurn(bool b,bool exclusive=true); + + + // load/save + /** + * Load a saved player, from file OR network. By default all + * KGameProperty objects in the dataHandler of this player are loaded + * and saved when using load or save. If you need to save/load more + * you have to replace this function (and save). You will probably + * still want to call the default implementation additionally! + * + * @param stream a data stream where you can stream the player from + * + * @return true? + */ + virtual bool load(QDataStream &stream); + + /** + * Save a player to a file OR to network. See also load + * + * @param stream a data stream to load the player from + * + * @return true? + */ + virtual bool save(QDataStream &stream); + + /** + * Receives a message + * @param msgid The kind of the message. See messages.txt for further + * information + * @param stream The message itself + * @param sender + **/ + void networkTransmission(QDataStream &stream,int msgid,Q_UINT32 sender); + + /** + * Searches for a property of the player given its id. + * @param id The id of the property + * @return The property with the specified id + **/ + KGamePropertyBase* findProperty(int id) const; + + /** + * Adds a property to a player. You would add all + * your player specific game data as KGameProperty and + * they are automatically saved and exchanged over network. + * + * @param data The property to be added. Must have an unique id! + * @return false if the given id is not valid (ie another property owns + * the id) or true if the property could be added successfully + **/ + bool addProperty(KGamePropertyBase* data); + + /** + * Calculates a checksum over the IO devices. Can be used to + * restore the IO handlers. The value returned is the 'or'ed + * value of the KGameIO rtti's. + * this is itnernally used for saving and restorign a player. + */ + int calcIOValue(); + + /** + * @return the property handler + */ + KGamePropertyHandler* dataHandler(); + +signals: + /** + * The player object got a message which was targeted + * at it but has no default method to process it. This + * means probably a user message. Connecting to this signal + * allowed to process it. + */ + void signalNetworkData(int msgid, const QByteArray& buffer, Q_UINT32 sender, KPlayer *me); + + /** + * This signal is emmited if a player property changes its value and + * the property is set to notify this change. This is an + * important signal as you should base the actions on a reaction + * to this property changes. + */ + void signalPropertyChanged(KGamePropertyBase *property,KPlayer *me); + +protected slots: + /** + * Called by KGameProperty only! Internal function! + **/ + void sendProperty(int msgid, QDataStream& stream, bool* sent); + /** + * Called by KGameProperty only! Internal function! + **/ + void emitSignal(KGamePropertyBase *me); + + +private: + void init(); + +private: + KGame *mGame; + bool mActive; // active player + KGameIOList mInputList; + + // GameProperty // AB: I think we can't move them to KPlayerPrivate - inline + // makes sense here + KGamePropertyBool mAsyncInput; // async input allowed + KGamePropertyBool mMyTurn; // Is it my turn to play (only useful if not async)? + KGamePropertyInt mUserId; // a user defined id + + KPlayerPrivate* d; +}; + +#endif diff --git a/libkdegames/kgame/libkdegames.html b/libkdegames/kgame/libkdegames.html new file mode 100644 index 00000000..66206c47 --- /dev/null +++ b/libkdegames/kgame/libkdegames.html @@ -0,0 +1,187 @@ +<html> + <head> + <title>Documentation for libkdegames</title> + <meta content=""> + <style></style> + </head> + <body> + <H1>Documentation for the classes in libkdegames</H1> +<!--------------------------------------------------------------------------------> + <H3>Design Principles</H3> + The library <em>kdegames</em> contains a collection of classes that can be used + to develop games using the KDE environment very easily. There are a few + principles that were used when developing the library:<P> + + <UL> + <LI><b>usable for a big variety of games</b><br> + The class <em>KGame</em> provides many features that are needed in many games. + It can be used for board games, card games, maze games, simulation games, strategy games and + many more.<br> + It does not (yet) include special features for realtime games, but can be used there, too. + <LI><b>features one-player and multi-player games</b></br> + A game developed with this library can easily support any number of simultaneous players. + So use it for one-player games (like Tetris or KSame), for two-player games (like TicTacToe + or chess) or for games with an arbitrary number of players. + <LI><b>computer players can easily be developed</b><br> + The class <em>KPlayer</em> represents an abstract player in a game. This can be a + human player that gets the input from the mouse or the keyboard. Or it can be a computer + player that makes moves by random or with artificial intelligence. All this can be achieved + subclassing KPlayer. + <LI><b>support for network games transparently</b><br> + The class <em>KGame</em> contains lots of features for network game support. Developing + a network game using a TCP/IP connection is very easy this way. But the default + case is still the local game. So the user should not need to connect to the internet + or to select a game server to play a game. + <LI><b>support for turn based and for asynchronous games</b><br> + You can use this library for turn based games, when only one player can make a move at a time + (like most bord games or card games), but also for asynchronous games, when every player can + make a move any time (like many action games). + </UL> +<!--------------------------------------------------------------------------------> + <H3>The central game class: <em>KGame</em></H3> + + When you want to develop your own KDE game using the KDE games library, you most likely want + to use the <em>KGame</em> class. There are two possible ways to extend it to your own needs: + Create a subclass and overwrite the virtual methods, or simply create an instance of KGame + and connect to the appropriate signals.<P> + + <<more code about KGame, an easy example>> + +<!--------------------------------------------------------------------------------> + <H3>Network games and related classes</H3> + + One of the main principles in the design of <em>KGame</em> was to make network games possible + with a minimum of effort for the game developer.<P> + + A network game is a game with usually several players, that are on different computers. These + computers are usually connected to the internet, and all the moves a player does are exchanged + over this network.<P> + + The exchange of moves and other information is done using the class <em>KMessageServer</em>. + An object of this class is a server that waits for connections. Someone who wants to take part + in the game has to connect to this server - usually using an internet socket connection. He does + this by creating a <em>KMessageClient</em> object. This object connects to the message server.<P> + + The transfer of data is realised by subclasses of the abstract class <em>KMessageIO</em>. One object + of this class is created on the client side, one on the server side. Different types of networks can + be supported by creating new subclasses of KMessageIO. There are already two subclasses of KMessageIO: + <em>KMessageSocket</em> uses a internet socket connection to transfer the data from the message client + to the message server or vice versa. <em>KMessageDirect</em> can be used if both the message server and + the message client are within the same process. The data blocks are copied directly to the other side, + so the transfer is faster and needs no network bandwidth.<P> + + A typical network game situation could look like this:<P> + + <IMG SRC="kmessageserver.png"><P> + + Here, three KGame object (called message clients) are connected to the KMessageServer object. One + is in the same process, so it uses KMessageDirect. The other two use KMessageSocket, so an internet + socket connection is used. KGame doesn't talk directly to the message server, but uses a KMessageClient + object instead. One of the KMessageClient objects is the admin of the message server. This client has some + priviledges. It may e.g. kill the connection to other message clients, or limit the number of clients + that may connect.<P> + + The KGame objects are by default all equal. So the usual approach will be that every KGame object will + store the complete status of the game, and any change or move will be broadcasted to the other KGame + objects, so that they all change the status in identical ways. Sometimes it may be necessary (or just + easier to implement) that one KGame object is a <em>game server</em> (i.e. he is repsonsible for everything, + he coordinates the complete game and stores the game status), whereas the other KGame objects are + only dumb stubs that are used to contact the <em>game server</em>. You can implement both approaches + using the message server structure. If you need to elect the KGame object that shall be + the game server, you may e.g. use the one that has the KMessageClient that is the admin of the message + server. (Of course this is only a suggestion, you can use other approaches.)<P> + + The main principle when developing the message server/client structure was, that the message server + doesn't have <em>any</em> idea of the game and its rules that is played. The message server only forwards + messages from one message client to the others without interpreting or manipulating the data. So always + keep in mind that the message server is <em>not</em> a game server! It does not store any data about + the game status. It is only a server for network connections and message broadcasting, <em>not</em> + for game purposes. The reason for this principle is, that <em>any</em> game can be played using a + KMessageServer on any computer. The computer being the message server doesn't need to know anything + about the game that is played on it. So you don't have to install new versions of the game there. Only + the clients need to be game specific.<P> + + Usually you don't need to create <em>KMessageServer</em> or <em>KMessageClient</em> objects in your game, + since <em>KGame</em> does this for you. There are three different scenarios fo network games that are + all supported in <em>KGame</em>:<P> + + <b>Scenario 1: local game</b><P> + + The local game should always be the default state a game should be in. To avoid having this scenario + as a special case, <em>KGame</em> automatically creates a KMessageServer object and a KMessageClient + object. So every change and every move is sent to the message server and is returned to the KGame + object before it is processed. Since the connection between the message client and the message server + uses KMessageDirect the data transfer is very fast and wont hurt in most cases.<P> + + <IMG SRC="scenario0.png"><P> + + This is the default situation right after creating the <em>KGame</em> object.<P> + + <b>Scenario 2: network game, started by one player</b><P> + + If one user is bored of playing alone, he can open his game for connections from the outside world. + He listens to a TCP/IP socket port (i.e. a number between 0 and 65535). Other players can create + KGame objects of their own and connect to this port. They need to know the IP address of that computer + and the port number. This situation will have this structure: + + <IMG SRC="scenario1.png"><P> + + The first player has to do something like:<P> + + <PRE> + KGame *myGame = new KGame (); + // wait for connections on port 12345 + myGame->offerConnections (12345); + </PRE> + + And the other players have to do something like:<P> + + <PRE> + KGame *myGame = new KGame (); + // connect to the message server + myGame->connectToServer ("theServer.theDomain.com", 12345); + </PRE> + + This automatically removes the message server in these KGame objects and connects to the given + one instead.<P> + + <b>Scenario 3: network game, using a stand alone message server</b><P> + + Sometimes it is not possible to let the message server run on one of the players computer. If e.g. all + the players have their computer in a local network that uses masquerading to contact the internet, + other computers cannot connect to them since the computer doesn't have a IP address to the outside + world. Then the only way to play a network game is to have a standalone KMessageServer object on + another server computer (somthing like "games.kde.org" e.g.). Since the KMessageServer isn't game + specific at all, every game can be played using it. There doesn't have to be any special software + installed on that server computer, only the program creating a KMessageServer object.<P> + + This scenario has some more advantages: The message server can be a well known meeting point to + start a game. This way one could play games against other players you never knew before. Furthermore + the game is stopped brutally when the program that contains the message server in scenario 2 is + quitted. (Migration of message servers is not yet implemented, but may be in the future.) Using a + stand alone message server, the players may enter and leave the game as they want. + + <IMG SRC="scenario2.png"><P> + + To create this scenario, a special KMessageServer program has to be started on the computer + that shall be the stand alone message server:<P> + + <PRE> + % kmessageserver -port=12345 + </PRE> + + The other games that want to connect have to do this (supposed the stand alone message server + has the IP address "games.kde.org"):<P> + + <PRE> + KGame *myGame = new KGame (); + // connect to the message server + myGame->connectToServer ("games.kde.org", 12345); + </PRE> + + + + +<!--------------------------------------------------------------------------------> + </body> +</html>
\ No newline at end of file diff --git a/libkdegames/kgame/messages.txt b/libkdegames/kgame/messages.txt new file mode 100644 index 00000000..151196d5 --- /dev/null +++ b/libkdegames/kgame/messages.txt @@ -0,0 +1,93 @@ +Message formats of libkdegames: +------------------------------- + +There are two different communication layers, using their own protocols: + + - the message layer (KMessageIO, KMessageServer, KMessageClient) + + This is used to send messages from one client (i.e. KGame object) + to an other one, to a group of other clients, or to all the clients + connected to the KMessageServer. The messages are arbitrary blocks + of data, of an arbitrary length. + This layer is an underlying protocol that isn't game specific at all. + You shouldn't need to know the message format of the packets. If you + want to extend the protocol, have a look into KMessageServer API + reference for a complete list of message types. + + - the game layer (KGame, KGameIO, KPlayer) + + This layer uses the message layer to send its specific message packets + between the objects of the game. + This layer contains the game specific messages (e.g. addPlayer, setupGame). + The rest of this file describes this layer. + + +Game Layer Messages: +-------------------- + + Application Cookie 16 Bit + Version 8 Bit + MsgId 16 Bit + SenderId 16 Bit + ReceiverId 16 Bit + Userdata + +The format of the messages is used internally and there is usually no reason why +you have to know what it is used for. But as usually != always here are some +comments on the format. Note that KGame is under development and the content of +this file could be obsolete. Please result the sourcecode for up-to-date +information. +Application Cookie is used to identify the application. This prevents a +chess game from talking to a poker game. +Version is the version of KNetworkGame, sender and receiver must be of the same +version. + library note: this could be a limitation, as KGame should be backward + compatible. Maybe change to version must be >= KNETWORKGAME or something less + restrictive +MsgId specifies the kind of the message data (see below). +SenderId is the id of the KGame/KPlayer object which sent the message, it is +coded like this: the lower 10 bits specify a player and the upper bit +represent the game id shifted by 10 bits. So we get +Id=playerId | (gameId<<10); +ReceiverId is the id of the receiver of the message. It can be either +a player, a game or a broadcast. For a broadcast the Id is set to 0 +in the other cases the coding is as with the senderId +Userdata is the data of the user (wow ;-)) + + +MsgId UserData +--------------------------------------------------------- +IdMessage user defined + +IdSetupGame Q_INT32 isServer + Q_INT32 maxPlayers + Q_INT32 newid (id for the new game) + Q_INT32 cntR (virtual player nunmber) + Q_INT32 cntT (tagged player number) + TODO: Changed + +IdContinueSetup: TODO + +IdSendPlayer Q_INT32 omit how many tagged players for replacement + TODO: Changed + +IdGameSave Save(msg)->Load(msg) + +IdAddPlayer rtti + gameid() of the owner + player->Save(msg) -> player->Load(msg) + +IdRemovePlayer Q_INT16 playerid + +IdError Q_INT32 errorcode + QString errortext + +IdGameStatus Q_INT32 status + +IdPlayerProperty Q_INT16 propertyId + user defined -> the property + +IdGameProperty Q_INT16 propertyId + user defined -> the property + +IdPlayerInput user defined diff --git a/libkdegames/kgame/scenario0.png b/libkdegames/kgame/scenario0.png Binary files differnew file mode 100644 index 00000000..4de99b52 --- /dev/null +++ b/libkdegames/kgame/scenario0.png diff --git a/libkdegames/kgame/scenario1.png b/libkdegames/kgame/scenario1.png Binary files differnew file mode 100644 index 00000000..74af4f6f --- /dev/null +++ b/libkdegames/kgame/scenario1.png diff --git a/libkdegames/kgame/scenario2.png b/libkdegames/kgame/scenario2.png Binary files differnew file mode 100644 index 00000000..14ea0a3c --- /dev/null +++ b/libkdegames/kgame/scenario2.png |