|
|
|
/*
|
|
|
|
This file is part of the KDE games library
|
|
|
|
Copyright (C) 2001 Andreas Beckermann (b_mann@gmx.de)
|
|
|
|
Copyright (C) 2001 Martin Heni (martin@heni-online.de)
|
|
|
|
|
|
|
|
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 <tqdatastream.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 <b_mann@gmx.de>
|
|
|
|
**/
|
|
|
|
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 (TQ_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 TQObject 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(TQDataStream& s) = 0;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Write the value into a stream. MUST be overwritten
|
|
|
|
**/
|
|
|
|
virtual void save(TQDataStream& 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(TQDataStream &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, TQString 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, TQString 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, TQString 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, TQString 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, TQString 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 TQByteArray& 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:TQTimer
|
|
|
|
// 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
|
|
|
|
* TQTimer::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 TQTimer::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 TQDataStream:
|
|
|
|
* \code
|
|
|
|
* class Card
|
|
|
|
* {
|
|
|
|
* public:
|
|
|
|
* int type;
|
|
|
|
* int suite;
|
|
|
|
* };
|
|
|
|
* TQDataStream& operator<<(TQDataStream& stream, Card& card)
|
|
|
|
* {
|
|
|
|
* TQ_INT16 type = card.type;
|
|
|
|
* TQ_INT16 suite = card.suite;
|
|
|
|
* s << type;
|
|
|
|
* s << suite;
|
|
|
|
* return s;
|
|
|
|
* }
|
|
|
|
* TQDataStream& operator>>(TQDataStream& stream, Card& card)
|
|
|
|
* {
|
|
|
|
* TQ_INT16 type;
|
|
|
|
* TQ_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 <b_mann@gmx.de>
|
|
|
|
**/
|
|
|
|
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 TQDataStream 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;
|
|
|
|
}
|
|
|
|
TQByteArray b;
|
|
|
|
TQDataStream 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(TQDataStream &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(TQDataStream& 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<TQString> KGamePropertyTQString;
|
|
|
|
typedef KGameProperty<TQ_INT8> KGamePropertyBool;
|
|
|
|
|
|
|
|
#endif
|