1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 """
21 This module contains Bzoo Client object definition
22
23 """
24 import socket
25 import sys
26 import struct
27
28 import bzMessage
29
30 import bzException
31 import bzTools
32 import bzInventory
33 import bzPicture
34 from bzPicture import bzPicture
35 import bzProtocol
36 from bzProtocol import bzProtocol
37
38
40 """
41 This object deals with socket connection and low level data exchanges
42
43 @ivar servername: servername
44 @type servername: string.
45 @ivar port: port number of socket
46 @type port: integer
47 @ivar socket : the socket
48 @type socket: socket
49 @ivar connected = False
50 @type connected: boolean
51 @ivar sa: sockaddr
52 @type sa: ???
53 @ivar receive_status: indicates whether last read action succeeded.
54 @type receive_status: boolean
55 """
56
57 - def __init__ (self, servername='127.0.0.1', port=50000):
58 """
59 @param servername: The server hostname or IP adress
60 @type servername: String.
61 @param port: The port of the server to connect to
62 @type port: Integer
63 @return: N/A
64 @rtype: L{bzClient}
65 """
66 self.servername = servername
67 self.port = port
68 self.socket = None
69 self.connected = False
70
71 self.socket = None
72 self.sa = None
73 self.receive_status = False
74 for res in socket.getaddrinfo(self.servername, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM):
75 af, socktype, proto, canonname, self.sa = res
76 try:
77 self.socket = socket.socket(af, socktype, proto)
78 except socket.error, msg:
79 self.socket = None
80 continue
81
84
85
87 """
88 Attempts to connect to server.
89 Socket is supposed not to be in blocking mode.
90 @return: True if connection was successfully established .. False otherwise
91 @rtype: Boolean
92 """
93 try:
94 self.socket.connect(self.sa)
95 self.connected = True
96 except socket.error, msg:
97 self.socket.close()
98
99 self.connected = False
100
101
102 return (self.connected)
103
104
105
106
108 """
109 Attempts to receive a data packet from server.
110 Socket is supposed not to be in blocking mode.
111 @return: received data if attempt succeed.. None otherwise
112 @rtype: Byte array or None
113 """
114 data = None
115 try:
116 self.receive_status = False
117 data = self.socket.recv(1024)
118 self.receive_status = True
119 except socket.error, (errno, msg):
120
121 if (errno == 10054) or (errno == 10053):
122 self.receive_status = False
123 self.connected=False
124
125 return data
126
128 """
129 Send a raw data packet to server.
130 @param data: the data to send to server.
131 @type data: byte array.
132 """
133 try:
134 self.socket.send(data)
135 except socket.error, (errno, msg):
136
137 if (errno == 10054) or (errno == 10053):
138 self.connected=False
139
141 """
142 Close connection with server by closing socket
143 """
144 self.socket.close()
145 self.connected = False
146
148 """
149 Indicates if socket is opened
150 @return: a boolean indicating whether socket is connected or not
151 @rtype: Boolean
152 """
153 return self.connected
154
156 """
157 A connection to the server
158
159 @ivar id: the identifier given by server
160 @type id: integer
161 @ivar pseudo: the pseudo used for the connection
162 @type pseudo: string.
163 @ivar password: the password used for this connection
164 @type password: string.
165 """
166
167 - def __init__ (self, hostname='127.0.0.1', port=50000):
168 """
169 @param hostname: The server hostname or IP adress
170 @type hostname: String.
171 @param port: The port of the server to connect to
172 @type port: Integer
173 @return: N/A
174 @rtype: bzClient
175 """
176 self.id = 0
177 self.pseudo = 'Guest'
178 self.password = ''
179 bzClientSocket.__init__(self, hostname, port)
180
183
185 """
186 Open a session on the server by:
187 sending an ACCEPT String
188 receiving a WelcomeMessage
189 throw bzException.BZooConnectionError
190 @param pseudo: the login to use for this session.
191 @type pseudo: String.
192 @param password: the password.
193 @type password: String.
194 @return: [realid, result, reason, effpseudo, position , orientation , heading, state,action, scene]
195 @rtype: [Int, Int, Int , string, vector , matrix , matrix, Int ,Int, Int]
196 """
197 realid = 0
198 bzClientSocket.Connect(self)
199
200 self.pseudo = pseudo
201 self.password = password
202
203 if self.Connected():
204 print "Connection established"
205 chaine = 'ACCEPT:%s:%s'%(self.pseudo, self.password)
206 self.SendData(chaine)
207 data = self.ReceiveData ()
208
209 nbbyte, [realid, result, reason, effpseudo, position , orientation , heading, state,action,scene] = bzMessage.WelcomeMessage().Decode(data)
210
211 self.id = realid
212 self.pseudo = effpseudo
213
214 if (result != 0):
215
216 print 'Connection refused . Serveur full ...'
217 raise bzException.BZooConnectionError("Connection refused")
218 else:
219 print 'Connected. My pseudo is: %s, my id is %d,'%(self.pseudo, self.id)
220 self.socket.setblocking(0)
221 else:
222 raise bzException.BZooConnectionError("Unable to connect")
223 return (realid, result, reason, effpseudo, position , orientation , heading, state,action,scene)
224
226 """
227 Close connection with server
228 """
229 self.CloseConnection()
230
231
232
233 -class bzClient(bzProtocol, bzConnection, bzPicture):
234 """
235 This class is the main canevas for a BZoo client application object.
236
237
238 The Hello World program.
239 ========================
240
241 The simplest BZoo client.
242 -------------------------
243
244
245 Source::
246 import bzClient
247
248 login = 'MyLogin'
249 password = 'MyPassword'
250
251 aClient = bzClient.bzClient(hostname='127.0.0.1', port=50001, pseudo=login)
252 aClient.OpenSession(login, password)
253 aClient.SayMessage('Hello World')
254 aClient.CloseSession()
255
256 This simple program opens a connection with the server that should be running on the local computer.
257 Next, it opens a session and send a "Hello world" message to others.
258 The message should appear in the chat windows of all connected people currently playing in the same scene.
259 Finally, it closes the session with the server.
260
261 It is to be noted that in this basic example, no control is made upon connection results.
262 Moreover, it is a very short session. The connection only last the time to say "Hello World" .
263
264 Entertaining connection.
265 ------------------------
266 Let's see a more complete example.
267
268
269 Sources::
270 class ThreadClient ( threading.Thread, bzClient.bzClient ):
271
272 def __init__ ( self , login, passwd):
273 bzClient.bzClient.__init__(self, hostname='127.0.0.1', port=50001, pseudo=login)
274 [result, reason] = self.OpenSession(login, passwd)
275 print "Connection result = ", [result, reason]
276 threading.Thread.__init__(self)
277 self.running = False
278
279
280 def run ( self ):
281 self.running = True
282 while (self.running == True):
283 self.Pulse()
284 time.sleep (0.35)
285
286 def stop ( self ):
287 self.running = False
288 self.CloseConnection()
289
290 login = 'MyLogin'
291 passwd = 'MyPassword'
292 client = ThreadClient(login, passwd)
293 client.start ()
294
295 In practice, when using BZoo client within Blender Game engine, you don't have to deal with thread.
296 The game engine does it for you.
297 Thus, in a Blender game, a typical use of bzClient object could be to:
298 - Create an object with an allways sensor
299 - Attach this sensor to a Python controller with the following content:
300
301 Sources::
302
303 if hasattribute(GameLogic,"BClient") == False:
304 GameLogic.BClient = bzClient.bzClient(hostname='127.0.0.1', port=50001, pseudo=login)
305 GameLogic.BClient.OpenSession(login, password)
306 GameLogic.BClient.SayMessage('Hello World')
307 else:
308 GameLogic.BClient.Pulse()
309
310 The program above, when cyclically called by an always sensor, first creates a bzClient if it doesn't already exist and start a session on server.
311 It entertains the connection with the server by periodically calling the C{Pulse()} method of bzClient Object it it exists.
312
313
314 Depicting the current world situation.
315 ======================================
316 The goal of the connection we depicted above is to provide a way, for a BZoo Client program, to get informed of what other client situation (their position, oritentation, their current action, and so on).
317 Thanks to these informations, a BZoo client application can control the game engine to make it depicting the whole game context.
318 The principle relies on a periodic exchange of information between client and server that runs in 3 steps:
319 - The client reports his situation to the server.
320 - Server, as a reply, gives it the report of other client situation.
321 - The client update scene content in the game engine.
322 Information exchanges are handled and monitored by the bzClient.Pulse() function.
323 The BZoo programmer is only in charge of providing updated information about player situation so that the reporting process can send them to the server.
324 Typical information are:
325 - Current position
326 - Current orientation
327 - Current heading
328 - Current status
329 - Current action
330 - Current visited scene
331 the bzClient provides method to
332 It also provides functions to read equivalent report from other players.
333 The two following sections describe the way to refresh player current information and
334 The third section talks about the exchange that can occurs aperiodically
335
336
337 Getting information on game context
338 -----------------------------------
339
340 TO BE WRITTEN
341
342 Sources::
343
344 if hasattribute(GameLogic,"BClient") == True:
345 nbc = GameLogic.BClient.GetNbClients()
346 print "there are %d people in this game scene"%nbc
347
348 inventory = GameLogic.BClient.GetInventoryContent()
349 print "My inventory content is : ", inventory
350
351
352 Updating your information on server
353 -----------------------------------
354 TO BE WRITTEN
355
356 Sources::
357
358 if hasattribute(GameLogic,"BClient") == True:
359 GameLogic.BClient.StorePosition([posX, posY, posZ])
360 GameLogic.BClient.StoreScene()
361
362 The above example only shows the position updating.
363 Similar call StorePosition() StoreOrientation() and StoreHeading()
364
365 Aperiodic exchanges
366 -------------------
367 TO BE WRITTEN
368
369 Sources::
370 if hasattribute(GameLogic,"BClient") == True:
371
372 GameLogic.BClient.GrabObject(1, "Lamp")
373 GameLogic.BClient.SayMessage("I've just grabbed a lamp!")
374
375 GameLogic.BClient.ActivateObject("Door", 1)
376 GameLogic.BClient.SayMessage("I've just opened the door!")
377
378
379 Getting closer from the protocol.
380 =================================
381
382 Using Network Message Handler
383 -----------------------------
384 TO BE WRITTEN
385
386 Using System Command
387 --------------------
388 TO BE WRITTEN
389
390
391 Extending protocol
392 ------------------
393 TO BE WRITTEN
394
395
396 @ivar position: the current position of the player
397 @type position: 3*1 Vector
398 @ivar orientation: the current orientation of the player
399 @type orientation: 3*3 Matrix
400 @ivar start_position : the start position
401 @type start_position: 3*1 Vector
402 @ivar start_orientation = the start orientation
403 @type start_orientation: 3*3 Matrix.
404 @ivar start_scene: the scene number to start in.
405 @type start_scene: integer.
406 @ivar heading: the current heading of the player.
407 @type heading: 3*3 Matrix.
408 @ivar action: the current action of the player (???).
409 @type action: integer
410 @ivar scene: the scene the player is currently in.
411 @type scene: integer
412 @ivar state: the state the player is in (???).
413 @type state: integer
414 @ivar id_avatar: the number of the player's avatar
415 @type id_avatar: integer
416 @ivar inventory: the player's inventory
417 @type inventory: L{bzInventory.bzInventory}
418 @ivar message_queue: the message to be sent.
419 @type message_queue: array of L????
420 @ivar display: the ????
421 @type display: L{bzTools.bzDisplay}
422
423 @ivar pseudos: list of pseudos
424 @type pseudos: list of string
425 @ivar positions: list of players'positions
426 @type positions: list of 3*1 vector
427 @ivar orientations: list of players'orientations
428 @type orientations: list of 3*3 matrix
429 @ivar headings: list of players'headings
430 @type headings: list of 3*3 matrix
431 @ivar states: lsit of player's states
432 @type states: list of integer
433 @ivar actions: list of players'current actions
434 @type actions: list of integer
435 @ivar scenes: list of scene player's are in.
436 @type scenes: list of integer
437 @ivar avatars: list of players'avatar id
438 @type avatars: list of integer
439 @ivar object_activation: dictionnary of action upon object.
440 @type object_activation:
441
442
443 """
444
445 - def __init__ (self, hostname='127.0.0.1', port=50000, pseudo='Guest'):
446 """
447 @param hostname: The server hostname or IP adress
448 @type hostname: String.
449 @param port: The port of the server to connect to
450 @type port: Integer
451 @param pseudo: The pseudo of the player
452 @type pseudo: Character string.
453 @return: N/A
454 @rtype: bzClient
455 """
456 self.hostname = hostname
457 self.port = port
458 self.pseudo = pseudo
459 self.id = 0
460
461 self.position = [0.0, 0.0, -20.0]
462 self.orientation = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
463 self.start_position = [0.0, 0.0, 4.0]
464 self.start_orientation = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
465 self.start_scene = 0
466 self.heading = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
467 self.action = 0
468 self.scene = 0
469 self.state = 0
470 self.id_avatar = 0
471 self.inventory = bzInventory.bzInventory()
472 self.message_queue = []
473 self.display=bzTools.bzDisplay()
474
475 self.pseudos = []
476 self.positions = []
477 self.orientations = []
478 self.headings = []
479 self.states = []
480 self.actions = []
481 self.scenes = []
482 self.avatars = []
483
484 self.object_activation = {}
485
486
487
488 bzConnection.__init__(self,hostname, port)
489 bzProtocol.__init__(self)
490 bzPicture.__init__(self)
491
492 self.addHandler(bzMessage.SITUATION_REPORT_MESSAGE,self.ClientsSituationReportEvent)
493 self.addHandler(bzMessage.CLIENTS_MESSAGES_MESSAGE,self.ClientsMessagesEvent)
494 self.addHandler(bzMessage.VANISH_OBJECT_MESSAGE,self.VanishObjectEvent)
495 self.addHandler(bzMessage.INSTANCIATE_OBJECT_MESSAGE,self.InstanciateObjectEvent)
496 self.addHandler(bzMessage.ACTIVATE_OBJECT_MESSAGE,self.ActivateObjectEvent)
497 self.addHandler(bzMessage.INVENTORY_ADD_MESSAGE,self.InventoryAddEvent)
498 self.addHandler(bzMessage.INIT_INVENTORY_MESSAGE, self.InitInventoryEvent)
499
501 """
502 Handles reception of a Activate Object Message
503 @param values: [objname, action]
504 @type values: [String, Integer].
505 """
506
507 [objname, action] = values
508 self.object_activation [objname] = action
509 pass
510
512 """
513 Handles reception of a Init Inventory Message
514 @param values: [list of [object_name, quantity]]
515 @type values: [list of [String, Integer]].
516 """
517 print "InitInventoryEvent, values=", values
518 [inventory] = values
519 self.inventory.getContent().extend(inventory)
520 pass
521
523 """
524 Handles reception of a Clients Message
525 @param values: [pseudo, message]
526 @type values: [String, String].
527 """
528
529 messages = values
530
531 idx_client = self.ids.index(messages[0])
532 self.display.QueueMessage(messages[1], self.pseudos[idx_client])
533
535 """
536 Handles reception of a Clients Situation Report.
537 @param values: [ ids, pseudos, positions, orientations, headings, states, actions, scenes, avatars]
538 @type values: [ Int[],String[], Vector[], Matrix[], Matrix[], Int[], Int[], Int[], Int[]].
539 """
540
541 [self.ids, self.pseudos, self.positions, self.orientations, self.headings, self.states, self.actions, self.scenes, self.avatars] = values
542 lenewid = self.ids[self.pseudos.index(self.pseudo)]
543 if lenewid != self.id:
544 self.id = self.ids[self.pseudos.index(self.pseudo)]
545 print "my new id is %d"%(self.id)
546 lenewid_avatar = self.avatars[self.pseudos.index(self.pseudo)]
547 if lenewid_avatar != self.id_avatar:
548 self.id_avatar = self.avatars[self.pseudos.index(self.pseudo)]
549 print "my new id is %d"%(self.id)
550
551
553 """
554 Handles reception of a Vanish Object message.
555 DO NOTHING !!
556 @param values: [ obj_name]
557 @type values: [ String].
558 """
559
560 [objname] = values
561
563 """
564 Handles reception of an Inventory Add message.
565 @param values: [ quantity, obj_name]
566 @type values: [ Int, String].
567 """
568
569 [quantity, objname] = values
570
571 self.inventory.add(quantity, objname)
572
573
574
576 """
577 Handles reception of an Instanciate Object message.
578 DO NOTHING !!
579 @param values: [ quantity, obj_name]
580 @type values: [ Int, String].
581 """
582
583 [quantity, objname] = values
584
585
586
587
588
590 """
591 Open a session on the server by:
592 sending an ACCEPT String
593 Initialize scene, position, orientation, heading, and
594 throw bzException.BZooConnectionError
595 @param pseudo: the login to use for this session.
596 @type pseudo: String.
597 @param password: the password.
598 @type password: String.
599 @return: [result, reason]
600 @rtype: [Int, Int]
601 """
602 self.start_id, result, reason, self.pseudo, self.start_position , self.start_orientation , self.start_heading, self.start_state, self.start_action, self.start_scene = bzConnection.OpenSession (self, pseudo, password)
603 self.id = self.start_id
604
605
606 return([result, reason])
607
609 """
610 @return: The pseudo used in the session
611 @rtype: String
612 """
613 return (self.pseudo)
614
616 """
617 @return: The current id for this session
618 @rtype: Integer
619 """
620 return (self.id)
621
623 """
624 @return: The number of the scene the client is in
625 @rtype: Integer
626 """
627 return (self.scene)
628
630 """
631 Give the number of clients connected to the serveur.
632
633 @return: The number of people connected to the server
634 @rtype: Integer
635 """
636 return (len(self.pseudos))
637
639 """
640 Gives the content of the client's inventory
641
642 @return: the client's inventory (list of [Quantity, ObjectType])
643 @rtype: list of tuple [Integer, String]
644 """
645 return (self.inventory.getContent())
646
648 """
649 Gives the value of player's variable 'var_name'
650
651 @param var_name: the name of the variable.
652 @type var_name: String.
653 @return: the client's inventory (list of [Quantity, ObjectType])
654 @rtype: Integer
655 """
656 return (self.inventory.count(var_name))
657
659 """
660 Give the current position of the player associated with this client.
661 This position is the one that is sent.
662 This function must be periodically called to inform other players
663 of the player's current position
664
665 @param position: The current position of the Player
666 @type position: 3*1 Vector
667 """
668 self.position = [position[0], position[1], position[2]]
669
671 """
672 Set the current orientation of the player associated with this client
673
674 This orientation is the one that is sent to other clients
675
676 This function must be periodically called to inform other players
677 of the player's current orientation
678
679 @param orientation: The current orientation of the Player
680 @type orientation: 3*3 Matrix
681 """
682 self.orientation = [[orientation[0][0], orientation[0][1], orientation[0][2]],
683 [orientation[1][0], orientation[1][1], orientation[1][2]],
684 [orientation[2][0], orientation[2][1], orientation[2][2]]]
685
686
688 """
689 Set the current heading of the player associated with this client
690
691 This orientation is the one that is sent to other clients
692
693 This function must be periodically called to inform other players
694 of the player's current heading
695
696 @param heading: The current heading of the Player
697 @type heading: 3*3 Matrix
698 """
699 self.heading = [[heading[0][0], heading[0][1], heading[0][2]],
700 [heading[1][0], heading[1][1], heading[1][2]],
701 [heading[2][0], heading[2][1], heading[2][2]]]
702
703
705 """
706 Set the current scene of the player associated with this client
707
708 This orientation is the one that is sent to other clients
709
710 This function must be periodically called to inform other players
711 of the player's current scene
712
713 @param scene: The current scene of the Player
714 @type scene: Integer
715 """
716 self.scene = scene
717
718
719
720
722 """
723 Set the current action of the player associated with this client
724
725 This action is the one that is sent to other clients
726
727 This function must be periodically called to inform other players
728 of the player's current action
729
730 @param action: The current action of the Player
731 @type action: Integer
732 """
733 if self.action != action:
734 self.action = action
735
737 """
738 Set the current state of the player associated with this client
739
740 This state is the one that is sent to other clients
741
742 This function must be periodically called to inform other players
743 of the player's current state
744
745 @param state: The current state of the Player
746 @type state: Integer
747 """
748 if self.state != state:
749 self.state = state
750
751
753 """
754 Performs an exchange with server.
755
756 Player send information about his position and orientation and collect other client's information from server
757 This function must be periodically called (at each game TIC)
758 """
759 data = None
760
761 try:
762
763 self.message_queue.append(bzMessage.PositionReport(self.position, self.orientation , self.heading, self.state ,self.action , self.scene ))
764
765
766 data2send = self.getNextDataMessage(self.message_queue, 1024)
767
768 self.SendData (data2send)
769
770
771 self.action = self.action & 0xFD
772
773
774 data = self.ReceiveData ()
775 if (self.receive_status == True) and (data is not None):
776 self.processDataMessage(data)
777
778 except socket.error, msg:
779 print "Exception %s %s"%(socket.error, msg)
780
781
782
783
784 - def Say (self, message):
785 """
786 Send a text message to all players connected to the server
787
788 @param message: The message to be sent to other players
789 @type message: String
790 """
791 self.message_queue.append(bzMessage.SayMessage(message))
792
793
795 """
796 Set the avatar choosen by the player
797
798 This function is called when player choose is avatar
799
800 @param id_avatar: The id of the choosen avatar
801 @type id_avatar: Integer
802 """
803 self.id_avatar = id_avatar
804 self.message_queue.append(bzMessage.StoreAvatar(self.id_avatar))
805
807 """
808 Send a text message to all players connected to the server
809
810 @param pseudo: The pseudo of the player to sent the message to
811 @type pseudo: String
812 @param message: The message to be sent to other players
813 @type message: String
814 """
815 self.message_queue.append(bzMessage.PrivateSayMessage(pseudo, message))
816
818 """
819 Make the client grab an object from the scene
820 @param quantity: The quantity of grabbed object
821 @type quantity: String
822 @param objname: The name of the grabbed object
823 @type objname: String
824 """
825 self.message_queue.append(bzMessage.GrabObject(quantity,objname))
826
828 """
829 Declare a player variable onto the server.
830 It is used to inform the server of a variable that has to be dealt with for this player "Health", "Strength", ...
831 @param quantity: The initialisation value of variable
832 @type quantity: Int
833 @param varname: The name of the variable
834 @type varname: String
835 """
836 self.message_queue.append(bzMessage.DeclareVar(quantity,varname))
837
838
839 - def ReleaseObject (self, objname='Lamp', position = [0.0, 0.0, 0.0]):
840 """
841 Make the client release an object from the scene at a given position.
842
843
844 @param objname: The name of the released object
845 @type objname: String
846 @param position: The location where the object is released
847 @type position: String
848 """
849 self.message_queue.append(bzMessage.ReleaseObject(objname, position))
850
851
853 """
854 Make the client activate an object from the scene
855
856 @param objname: The name of the activated object
857 @type objname: String
858 @param value: The activation value
859 @type value: Integer
860 """
861 self.message_queue.append(bzMessage.ActivateObject(objname,value))
862
864 """
865 Make the client send a SAVEME command to the server
866
867 """
868 self.message_queue.append(bzMessage.SayMessage("$SAVEME"))
869
870 if __name__ == "__main__":
871
872 aClient = bzClient(hostname='127.0.0.1', port=50000, pseudo='Guest')
873 aClient.OpenSession('coucou','pass')
874 del aClient
875