Mercurial > traipse_dev
comparison orpg/networking/mplay_client.py @ 0:4385a7d0efd1 grumpy-goblin
Deleted and repushed it with the 'grumpy-goblin' branch. I forgot a y
author | sirebral |
---|---|
date | Tue, 14 Jul 2009 16:41:58 -0500 |
parents | |
children | d5e81dac98ff |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4385a7d0efd1 |
---|---|
1 # Copyright (C) 2000-2001 The OpenRPG Project | |
2 # | |
3 # openrpg-dev@lists.sourceforge.net | |
4 # | |
5 # This program is free software; you can redistribute it and/or modify | |
6 # it under the terms of the GNU General Public License as published by | |
7 # the Free Software Foundation; either version 2 of the License, or | |
8 # (at your option) any later version. | |
9 # | |
10 # This program is distributed in the hope that it will be useful, | |
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 # GNU General Public License for more details. | |
14 # | |
15 # You should have received a copy of the GNU General Public License | |
16 # along with this program; if not, write to the Free Software | |
17 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
18 # -- | |
19 # | |
20 # File: mplay_client.py | |
21 # Author: Chris Davis | |
22 # Maintainer: | |
23 # Version: | |
24 # $Id: mplay_client.py,v 1.71 2007/05/12 20:41:54 digitalxero Exp $ | |
25 # | |
26 # Description: This file contains the code for the client stubs of the multiplayer | |
27 # features in the orpg project. | |
28 # | |
29 | |
30 __version__ = "$Id: mplay_client.py,v 1.71 2007/05/12 20:41:54 digitalxero Exp $" | |
31 | |
32 import orpg.minidom | |
33 import socket | |
34 import Queue | |
35 import thread | |
36 import traceback | |
37 from threading import Event, Lock | |
38 from xml.sax.saxutils import escape | |
39 from struct import pack, unpack, calcsize | |
40 from string import * | |
41 from orpg.orpg_version import * | |
42 import errno | |
43 import os | |
44 import time | |
45 | |
46 try: | |
47 import bz2 | |
48 cmpBZ2 = True | |
49 except: | |
50 cmpBZ2 = False | |
51 | |
52 try: | |
53 import zlib | |
54 cmpZLIB = True | |
55 except: | |
56 cmpZLIB = False | |
57 | |
58 | |
59 # This should be configurable | |
60 OPENRPG_PORT = 9557 | |
61 | |
62 # We should be sending a length for each packet | |
63 MPLAY_LENSIZE = calcsize( 'i' ) | |
64 MPLAY_DISCONNECTED = 0 | |
65 MPLAY_CONNECTED = 1 | |
66 MPLAY_DISCONNECTING = 3 | |
67 MPLAY_GROUP_CHANGE = 4 | |
68 MPLAY_GROUP_CHANGE_F = 5 | |
69 PLAYER_NEW = 1 | |
70 PLAYER_DEL = 2 | |
71 PLAYER_GROUP = 3 | |
72 | |
73 # The next two messages are used to inform others that a player is typing | |
74 PLAYER_TYPING = 4 | |
75 PLAYER_NOT_TYPING = 5 | |
76 PLAYER_UPDATE = 6 | |
77 GROUP_JOIN = 1 | |
78 GROUP_NEW = 2 | |
79 GROUP_DEL = 3 | |
80 GROUP_UPDATE = 4 | |
81 STATUS_SET_URL = 1 | |
82 | |
83 def parseXml(data): | |
84 "parse and return doc" | |
85 #print data | |
86 doc = orpg.minidom.parseString(data) | |
87 doc.normalize() | |
88 return doc | |
89 | |
90 def myescape(data): | |
91 return escape(data,{"\"":""}) | |
92 | |
93 class mplay_event: | |
94 def __init__(self,id,data=None): | |
95 self.id = id | |
96 self.data = data | |
97 | |
98 def get_id(self): | |
99 return self.id | |
100 | |
101 def get_data(self): | |
102 return self.data | |
103 | |
104 BOOT_MSG = "YoU ArE ThE WeAkEsT LiNk. GoOdByE." | |
105 | |
106 class client_base: | |
107 | |
108 # Player role definitions | |
109 def __init__(self): | |
110 self.outbox = Queue.Queue(0) | |
111 self.inbox = Queue.Queue(0) | |
112 self.startedEvent = Event() | |
113 self.exitEvent = Event() | |
114 self.sendThreadExitEvent = Event() | |
115 self.recvThreadExitEvent = Event() | |
116 self.id = "0" | |
117 self.group_id = "0" | |
118 self.name = "" | |
119 self.role = "GM" | |
120 self.ROLE_GM = "GM" | |
121 self.ROLE_PLAYER = "PLAYER" | |
122 self.ROLE_LURKER = "LURKER" | |
123 self.ip = socket.gethostbyname(socket.gethostname()) | |
124 self.remote_ip = None | |
125 self.version = VERSION | |
126 self.protocol_version = PROTOCOL_VERSION | |
127 self.client_string = CLIENT_STRING | |
128 self.status = MPLAY_DISCONNECTED | |
129 self.useCompression = False | |
130 self.compressionType = 'Undefined' | |
131 self.log_console = None | |
132 self.sock = None | |
133 self.text_status = "Idle" | |
134 self.statLock = Lock() | |
135 self.useroles = 0 | |
136 self.ROLE_GM = "GM" | |
137 self.ROLE_PLAYER = "PLAYER" | |
138 self.ROLE_LURKER = "LURKER" | |
139 self.lastmessagetime = time.time() | |
140 self.connecttime = time.time() | |
141 | |
142 def sendThread( self, arg ): | |
143 "Sending thread. This thread reads from the data queue and writes to the socket." | |
144 | |
145 # Wait to be told it's okay to start running | |
146 self.startedEvent.wait() | |
147 | |
148 # Loop as long as we have a connection | |
149 while( self.get_status() == MPLAY_CONNECTED ): | |
150 try: | |
151 readMsg = self.outbox.get( block=1 ) | |
152 except Exception, text: | |
153 self.log_msg( ("outbox.get() got an exception: ", text) ) | |
154 | |
155 # If we are here, it's because we have data to send, no doubt! | |
156 if self.status == MPLAY_CONNECTED: | |
157 try: | |
158 # Send the entire message, properly formated/encoded | |
159 sent = self.sendMsg( self.sock, readMsg ) | |
160 except Exception, e: | |
161 self.log_msg( e ) | |
162 else: | |
163 # If we are not connected, purge the data queue | |
164 self.log_msg( "Data queued without a connection, purging data from queue..." ) | |
165 self.sendThreadExitEvent.set() | |
166 self.log_msg( "sendThread has terminated..." ) | |
167 | |
168 def recvThread( self, arg ): | |
169 "Receiving thread. This thread reads from the socket and writes to the data queue." | |
170 | |
171 # Wait to be told it's okay to start running | |
172 self.startedEvent.wait() | |
173 | |
174 while( self.get_status() == MPLAY_CONNECTED ): | |
175 readMsg = self.recvMsg( self.sock ) | |
176 try: | |
177 if self.useCompression and self.compressionType != None: | |
178 readMsg = self.compressionType.decompress(readMsg) | |
179 except: | |
180 pass | |
181 | |
182 # Check the length of the message | |
183 bytes = len( readMsg ) | |
184 | |
185 # Make sure we are still connected | |
186 if bytes == 0: | |
187 break | |
188 else: | |
189 # Pass along the message so it can be processed | |
190 self.inbox.put( readMsg ) | |
191 self.update_idle_time() #update the last message time | |
192 if bytes == 0: | |
193 self.log_msg( "Remote has disconnected!" ) | |
194 self.set_status( MPLAY_DISCONNECTING ) | |
195 self.outbox.put( "" ) # Make sure the other thread is woken up! | |
196 self.sendThreadExitEvent.set() | |
197 self.log_msg( "recvThread has terminated..." ) | |
198 | |
199 def sendMsg( self, sock, msg ): | |
200 """Very simple function that will properly encode and send a message to te | |
201 remote on the specified socket.""" | |
202 | |
203 if self.useCompression and self.compressionType != None: | |
204 mpacket = self.compressionType.compress(msg) | |
205 lpacket = pack('!i', len(mpacket)) | |
206 sock.send(lpacket) | |
207 offset = 0 | |
208 while offset < len(mpacket): | |
209 slice = buffer(mpacket, offset, len(mpacket)-offset) | |
210 sent = sock.send(slice) | |
211 offset += sent | |
212 sentm = offset | |
213 else: | |
214 # Calculate our message length | |
215 length = len(msg) | |
216 | |
217 # Encode the message length into network byte order | |
218 lp = pack('!i', length) | |
219 | |
220 try: | |
221 # Send the encoded length | |
222 sentl = sock.send( lp ) | |
223 | |
224 # Now, send the message the the length was describing | |
225 sentm = sock.send( msg ) | |
226 if self.isServer(): | |
227 self.log_msg(("data_sent", sentl+sentm)) | |
228 except socket.error, e: | |
229 self.log_msg( e ) | |
230 except Exception, e: | |
231 self.log_msg( e ) | |
232 return sentm | |
233 | |
234 def recvData( self, sock, readSize ): | |
235 """Simple socket receive method. This method will only return when the exact | |
236 byte count has been read from the connection, if remote terminates our | |
237 connection or we get some other socket exception.""" | |
238 data = "" | |
239 offset = 0 | |
240 try: | |
241 while offset != readSize: | |
242 frag = sock.recv( readSize - offset ) | |
243 # See if we've been disconnected | |
244 rs = len( frag ) | |
245 if rs <= 0: | |
246 # Loudly raise an exception because we've been disconnected! | |
247 raise IOError, "Remote closed the connection!" | |
248 else: | |
249 # Continue to build complete message | |
250 offset += rs | |
251 data += frag | |
252 except socket.error, e: | |
253 self.log_msg( e ) | |
254 data = "" | |
255 return data | |
256 | |
257 def recvMsg( self, sock ): | |
258 """This method now expects to receive a message having a 4-byte prefix length. It will ONLY read | |
259 completed messages. In the event that the remote's connection is terminated, it will throw an | |
260 exception which should allow for the caller to more gracefully handle this exception event. | |
261 | |
262 Because we use strictly reading ONLY based on the length that is told to use, we no longer have to | |
263 worry about partially adjusting for fragmented buffers starting somewhere within a buffer that we've | |
264 read. Rather, it will get ONLY a whole message and nothing more. Everything else will remain buffered | |
265 with the OS until we attempt to read the next complete message.""" | |
266 | |
267 msgData = "" | |
268 try: | |
269 lenData = self.recvData( sock, MPLAY_LENSIZE ) | |
270 # Now, convert to a usable form | |
271 (length,) = unpack('!i', lenData) | |
272 # Read exactly the remaining amount of data | |
273 msgData = self.recvData( sock, length ) | |
274 if self.isServer(): | |
275 self.log_msg(("data_recv", length+4)) | |
276 # Make the peer IP address available for reference later | |
277 if self.remote_ip is None: | |
278 self.remote_ip = self.sock.getpeername() | |
279 except IOError, e: | |
280 self.log_msg( e ) | |
281 except Exception, e: | |
282 self.log_msg( e ) | |
283 return msgData | |
284 | |
285 def initialize_threads(self): | |
286 "Starts up our threads (2) and waits for them to make sure they are running!" | |
287 self.status = MPLAY_CONNECTED | |
288 self.sock.setblocking(1) | |
289 # Confirm that our threads have started | |
290 thread.start_new_thread( self.sendThread,(0,) ) | |
291 thread.start_new_thread( self.recvThread,(0,) ) | |
292 self.startedEvent.set() | |
293 | |
294 def disconnect(self): | |
295 self.set_status(MPLAY_DISCONNECTING) | |
296 self.log_msg("client stub " + self.ip +" disconnecting...") | |
297 self.log_msg("closing sockets...") | |
298 try: | |
299 self.sock.shutdown( 2 ) | |
300 except Exception, e: | |
301 print "Caught exception: " + str(e) | |
302 print | |
303 print "Continuing" | |
304 self.set_status(MPLAY_DISCONNECTED) | |
305 | |
306 def reset(self,sock): | |
307 self.disconnect() | |
308 self.sock = sock | |
309 self.initialize_threads() | |
310 | |
311 def update_role(self,role): | |
312 self.useroles = 1 | |
313 self.role = role | |
314 | |
315 def use_roles(self): | |
316 if self.useroles: | |
317 return 1 | |
318 else: | |
319 return 0 | |
320 def update_self_from_player(self, player): | |
321 try: | |
322 (self.name, self.ip, self.id, self.text_status, self.version, self.protocol_version, self.client_string,role) = player | |
323 except Exception, e: | |
324 print e | |
325 | |
326 # The IP field should really be deprecated as too many systems are NAT'd and/or behind firewalls for a | |
327 # client provided IP address to have much value. As such, we now label it as deprecated. | |
328 def toxml(self,action): | |
329 xml_data = '<player name="' + myescape(self.name) + '"' | |
330 xml_data += ' action="' + action + '" id="' + self.id + '"' | |
331 xml_data += ' group_id="' + self.group_id + '" ip="' + self.ip + '"' | |
332 xml_data += ' status="' + self.text_status + '"' | |
333 xml_data += ' version="' + self.version + '"' | |
334 xml_data += ' protocol_version="' + self.protocol_version + '"' | |
335 xml_data += ' client_string="' + self.client_string + '"' | |
336 xml_data += ' useCompression="' + str(self.useCompression) + '"' | |
337 if cmpBZ2 and (self.compressionType == 'Undefined' or self.compressionType == bz2): | |
338 xml_data += ' cmpType="bz2"' | |
339 elif cmpZLIB and (self.compressionType == 'Undefined' or self.compressionType == zlib): | |
340 xml_data += ' cmpType="zlib"' | |
341 else: | |
342 xml_data += ' cmpType="None"' | |
343 xml_data += ' />' | |
344 return xml_data | |
345 | |
346 def log_msg(self,msg): | |
347 if self.log_console: | |
348 self.log_console(msg) | |
349 # else: | |
350 # print "message", msg | |
351 | |
352 def get_status(self): | |
353 self.statLock.acquire() | |
354 status = self.status | |
355 self.statLock.release() | |
356 return status | |
357 | |
358 def my_role(self): | |
359 #Why create the three different objects? Why not just assign a value to self.role and use that? Prof_Ebral ponders. | |
360 if self.role == "GM": | |
361 return self.ROLE_GM | |
362 elif self.role == "Player": | |
363 return self.ROLE_PLAYER | |
364 elif self.role == "Lurker": | |
365 return self.ROLE_LURKER | |
366 return -1 | |
367 | |
368 def set_status(self,status): | |
369 self.statLock.acquire() | |
370 self.status = status | |
371 self.statLock.release() | |
372 | |
373 def isServer( self ): | |
374 # Return 1 if we are running as a server, else, return 0. | |
375 # This method must be overloaded by whomever derives from us | |
376 pass | |
377 | |
378 def __str__(self): | |
379 return "%s(%s)\nIP:%s\ngroup_id:%s\n" % (self.name, self.id, self.ip, self.group_id) | |
380 | |
381 # idle time functions added by snowdog 3/31/04 | |
382 def update_idle_time(self): | |
383 self.lastmessagetime = time.time() | |
384 | |
385 def idle_time(self): | |
386 curtime = time.time() | |
387 idletime = curtime - self.lastmessagetime | |
388 return idletime | |
389 | |
390 def idle_status(self): | |
391 idletime = self.idle_time() | |
392 idlemins = idletime / 60 | |
393 status = "Unknown" | |
394 if idlemins < 3: | |
395 status = "Active" | |
396 elif idlemins < 10: | |
397 status = "Idle ("+str(int(idlemins))+" mins)" | |
398 else: | |
399 status = "Inactive ("+str(int(idlemins))+" mins)" | |
400 return status | |
401 | |
402 def connected_time(self): | |
403 curtime = time.time() | |
404 timeoffset = curtime - self.connecttime | |
405 return timeoffset | |
406 | |
407 def connected_time_string(self): | |
408 "returns the time client has been connected as a formated time string" | |
409 ct = self.connected_time() | |
410 d = int(ct/86400) | |
411 h = int( (ct-(86400*d))/3600 ) | |
412 m = int( (ct-(86400*d)-(3600*h))/60) | |
413 s = int( (ct-(86400*d)-(3600*h)-(60*m)) ) | |
414 cts = zfill(d,2)+":"+zfill(h,2)+":"+zfill(m,2)+":"+zfill(s,2) | |
415 return cts | |
416 | |
417 #======================================================================== | |
418 # | |
419 # | |
420 # MPLAY CLIENT | |
421 # | |
422 # | |
423 #======================================================================== | |
424 class mplay_client(client_base): | |
425 "mplay client" | |
426 def __init__(self,name,callbacks): | |
427 client_base.__init__(self) | |
428 self.set_name(name) | |
429 self.on_receive = callbacks['on_receive'] | |
430 self.on_mplay_event = callbacks['on_mplay_event'] | |
431 self.on_group_event = callbacks['on_group_event'] | |
432 self.on_player_event = callbacks['on_player_event'] | |
433 self.on_status_event = callbacks['on_status_event'] | |
434 self.on_password_signal = callbacks['on_password_signal'] | |
435 # I know this is a bad thing to do but it has to be | |
436 # be done to use the unified password manager. | |
437 # Should really find a better solution. -- SD 8/03 | |
438 self.orpgFrame_callback = callbacks['orpgFrame'] | |
439 self.settings = self.orpgFrame_callback.settings | |
440 #self.version = VERSION | |
441 #self.protocol_version = PROTOCOL_VERSION | |
442 #self.client_string = CLIENT_STRING | |
443 self.ignore_id = [] | |
444 self.ignore_name = [] | |
445 self.players = {} | |
446 self.groups = {} | |
447 self.unique_cookie = 0 | |
448 self.msg_handlers = {} | |
449 self.core_msg_handlers = [] | |
450 self.load_core_msg_handlers() | |
451 | |
452 # implement from our base class | |
453 def isServer(self): | |
454 return 0 | |
455 | |
456 def get_chat(self): | |
457 return self.orpgFrame_callback.chat | |
458 | |
459 def set_name(self,name): | |
460 self.name = name | |
461 self.update() | |
462 | |
463 def set_text_status(self, status): | |
464 if self.text_status != status: | |
465 self.text_status = status | |
466 self.update() | |
467 | |
468 def set_status_url(self, url="None"): | |
469 self.on_status_event(mplay_event(STATUS_SET_URL,url)) | |
470 | |
471 def update(self, evt=None): | |
472 if self.status == MPLAY_CONNECTED: | |
473 self.outbox.put(self.toxml('update')) | |
474 self.inbox.put(self.toxml('update')) | |
475 | |
476 def get_group_info(self, id=0): | |
477 self.statLock.acquire() | |
478 id = self.groups[id] | |
479 self.statLock.release() | |
480 return id | |
481 | |
482 def get_my_group(self): | |
483 self.statLock.acquire() | |
484 id = self.groups[self.group_id] | |
485 self.statLock.release() | |
486 return id | |
487 | |
488 def get_groups(self): | |
489 self.statLock.acquire() | |
490 groups = self.groups.values() | |
491 self.statLock.release() | |
492 return groups | |
493 | |
494 def get_players(self): | |
495 self.statLock.acquire() | |
496 players = self.players.values() | |
497 self.statLock.release() | |
498 return players | |
499 | |
500 def get_player_info(self,id): | |
501 self.statLock.acquire() | |
502 player = self.players[id] | |
503 self.statLock.release() | |
504 return player | |
505 | |
506 def get_player_by_player_id(self,player): | |
507 players = self.get_players() | |
508 if self.players.has_key(player): | |
509 for m in players: | |
510 if player == m[2]: | |
511 return m | |
512 return -1 | |
513 | |
514 def get_id(self): | |
515 return self.id | |
516 | |
517 def get_my_info(self): | |
518 return (self.name, self.ip, self.id, self.text_status, self.version, self.protocol_version, self.client_string, self.role) | |
519 | |
520 def is_valid_id(self,id): | |
521 self.statLock.acquire() | |
522 value = self.players.has_key( id ) | |
523 self.statLock.release() | |
524 return value | |
525 | |
526 def clear_players(self,save_self=0): | |
527 self.statLock.acquire() | |
528 keys = self.players.keys() | |
529 for k in keys: | |
530 del self.players[k] | |
531 self.statLock.release() | |
532 | |
533 def clear_groups(self): | |
534 self.statLock.acquire() | |
535 keys = self.groups.keys() | |
536 for k in keys: | |
537 del self.groups[k] | |
538 self.statLock.release() | |
539 | |
540 def find_role(self,id): | |
541 return self.players[id].role | |
542 | |
543 def get_ignore_list(self): | |
544 try: | |
545 return (self.ignore_id, self.ignore_name) | |
546 except: | |
547 return (None, None) | |
548 | |
549 def toggle_ignore(self, id): | |
550 for m in self.ignore_id: | |
551 if `self.ignore_id[self.ignore_id.index(m)]` == `id`: | |
552 name = self.ignore_name[self.ignore_id.index(m)] | |
553 self.ignore_id.remove(m) | |
554 self.ignore_name.remove(name) | |
555 return (0,id,name) | |
556 self.ignore_name.append(self.players[id][0]) | |
557 self.ignore_id.append(self.players[id][2]) | |
558 return (1,self.players[id][2],self.players[id][0]) | |
559 | |
560 def boot_player(self,id,boot_pwd = ""): | |
561 #self.send(BOOT_MSG,id) | |
562 msg = '<boot boot_pwd="' + boot_pwd + '"/>' | |
563 self.send(msg,id) | |
564 | |
565 #--------------------------------------------------------- | |
566 # [START] Snowdog Password/Room Name altering code 12/02 | |
567 #--------------------------------------------------------- | |
568 | |
569 def set_room_pass(self,npwd,pwd=""): | |
570 self.outbox.put("<alter key=\"pwd\" val=\"" +npwd+ "\" bpw=\"" + pwd + "\" plr=\"" + self.id +"\" gid=\"" + self.group_id + "\" />") | |
571 self.update() | |
572 | |
573 def set_room_name(self,name,pwd=""): | |
574 loc = name.find("&") | |
575 oldloc=0 | |
576 while loc > -1: | |
577 loc = name.find("&",oldloc) | |
578 if loc > -1: | |
579 b = name[:loc] | |
580 e = name[loc+1:] | |
581 name = b + "&" + e | |
582 oldloc = loc+1 | |
583 loc = name.find('"') | |
584 oldloc=0 | |
585 while loc > -1: | |
586 loc = name.find('"',oldloc) | |
587 if loc > -1: | |
588 b = name[:loc] | |
589 e = name[loc+1:] | |
590 name = b + """ + e | |
591 oldloc = loc+1 | |
592 loc = name.find("'") | |
593 oldloc=0 | |
594 while loc > -1: | |
595 loc = name.find("'",oldloc) | |
596 if loc > -1: | |
597 b = name[:loc] | |
598 e = name[loc+1:] | |
599 name = b + "'" + e | |
600 oldloc = loc+1 | |
601 self.outbox.put("<alter key=\"name\" val=\"" + name + "\" bpw=\"" + pwd + "\" plr=\"" + self.id +"\" gid=\"" + self.group_id + "\" />") | |
602 self.update() | |
603 | |
604 #--------------------------------------------------------- | |
605 # [END] Snowdog Password/Room Name altering code 12/02 | |
606 #--------------------------------------------------------- | |
607 | |
608 def display_roles(self): | |
609 self.outbox.put("<role action=\"display\" player=\"" + self.id +"\" group_id=\""+self.group_id + "\" />") | |
610 | |
611 def get_role(self): | |
612 self.outbox.put("<role action=\"get\" player=\"" + self.id +"\" group_id=\""+self.group_id + "\" />") | |
613 | |
614 def set_role(self,player,role,pwd=""): | |
615 self.outbox.put("<role action=\"set\" player=\"" + player + "\" role=\"" +role+ "\" boot_pwd=\"" + pwd + "\" group_id=\"" + self.group_id + "\" />") | |
616 self.update() | |
617 | |
618 def send(self,msg,player="all"): | |
619 if self.status == MPLAY_CONNECTED and player != self.id: | |
620 self.outbox.put("<msg to='"+player+"' from='"+self.id+"' group_id='"+self.group_id+"' />"+msg) | |
621 self.check_my_status() | |
622 | |
623 def send_sound(self, snd_xml): | |
624 if self.status == MPLAY_CONNECTED: | |
625 self.outbox.put(snd_xml) | |
626 self.check_my_status() | |
627 | |
628 def send_create_group(self,name,pwd,boot_pwd,minversion): | |
629 self.outbox.put("<create_group from=\""+self.id+"\" pwd=\""+pwd+"\" name=\""+ | |
630 name+"\" boot_pwd=\""+boot_pwd+"\" min_version=\"" + minversion +"\" />") | |
631 | |
632 def send_join_group(self,group_id,pwd): | |
633 if (group_id != 0): | |
634 self.update_role("LURKER") | |
635 self.outbox.put("<join_group from=\""+self.id+"\" pwd=\""+pwd+"\" group_id=\""+str(group_id)+"\" />") | |
636 | |
637 def poll(self, evt=None): | |
638 try: | |
639 msg = self.inbox.get_nowait() | |
640 except: | |
641 if self.get_status() != MPLAY_CONNECTED: | |
642 self.check_my_status() | |
643 else: | |
644 try: | |
645 self.pretranslate(msg) | |
646 except Exception, e: | |
647 print "The following message: " + str(msg) | |
648 print "created the following exception: " | |
649 traceback.print_exc() | |
650 | |
651 def add_msg_handler(self, tag, function, core=False): | |
652 if not self.msg_handlers.has_key(tag): | |
653 self.msg_handlers[tag] = function | |
654 if core: | |
655 self.core_msg_handlers.append(tag) | |
656 else: | |
657 print 'XML Messages ' + tag + ' already has a handler' | |
658 | |
659 def remove_msg_handler(self, tag): | |
660 if self.msg_handlers.has_key(tag) and not tag in self.core_msg_handlers: | |
661 del self.msg_handlers[tag] | |
662 else: | |
663 print 'XML Messages ' + tag + ' already deleted' | |
664 | |
665 def load_core_msg_handlers(self): | |
666 self.add_msg_handler('msg', self.on_msg, True) | |
667 self.add_msg_handler('ping', self.on_ping, True) | |
668 self.add_msg_handler('group', self.on_group, True) | |
669 self.add_msg_handler('role', self.on_role, True) | |
670 self.add_msg_handler('player', self.on_player, True) | |
671 self.add_msg_handler('password', self.on_password, True) | |
672 self.add_msg_handler('sound', self.on_sound, True) | |
673 | |
674 def pretranslate(self,data): | |
675 # Pre-qualify our data. If we don't have atleast 5-bytes, then there is | |
676 # no way we even have a valid message! | |
677 if len(data) < 5: | |
678 return | |
679 end = data.find(">") | |
680 head = data[:end+1] | |
681 msg = data[end+1:] | |
682 xml_dom = parseXml(head) | |
683 xml_dom = xml_dom._get_documentElement() | |
684 tag_name = xml_dom._get_tagName() | |
685 id = xml_dom.getAttribute("from") | |
686 if id == '': | |
687 id = xml_dom.getAttribute("id") | |
688 if self.msg_handlers.has_key(tag_name): | |
689 self.msg_handlers[tag_name](id, data, xml_dom) | |
690 else: | |
691 #Unknown messages recived ignoring | |
692 #using pass insted or printing an error message | |
693 #because plugins should now be able to send and proccess messages | |
694 #if someone is using a plugin to send messages and this user does not | |
695 #have the plugin they would be getting errors | |
696 pass | |
697 if xml_dom: | |
698 xml_dom.unlink() | |
699 | |
700 def on_sound(self, id, data, xml_dom): | |
701 (ignore_id,ignore_name) = self.get_ignore_list() | |
702 for m in ignore_id: | |
703 if m == id: | |
704 # yes we are | |
705 print "ignoring sound from player:" | |
706 return | |
707 chat = self.get_chat() | |
708 snd = xml_dom.getAttribute("url") | |
709 loop_sound = xml_dom.getAttribute("loop") | |
710 chat.sound_player.play(snd, "remote", loop_sound) | |
711 | |
712 def on_msg(self, id, data, xml_dom): | |
713 end = data.find(">") | |
714 head = data[:end+1] | |
715 msg = data[end+1:] | |
716 if id == "0": | |
717 self.on_receive(msg,None) # None get's interpreted in on_receive as the sys admin. | |
718 # Doing it this way makes it harder to impersonate the admin | |
719 else: | |
720 if self.is_valid_id(id): | |
721 self.on_receive(msg,self.players[id]) | |
722 if xml_dom: | |
723 xml_dom.unlink() | |
724 | |
725 def on_ping(self, id, msg, xml_dom): | |
726 #a REAL ping time implementation by Snowdog 8/03 | |
727 # recieves special server <ping time="###" /> command | |
728 # where ### is a returning time from the clients ping command | |
729 #get current time, pull old time from object and compare them | |
730 # the difference is the latency between server and client * 2 | |
731 ct = time.clock() | |
732 ot = xml_dom.getAttribute("time") | |
733 latency = float(float(ct) - float(ot)) | |
734 latency = int( latency * 10000.0 ) | |
735 latency = float( latency) / 10.0 | |
736 ping_msg = "Ping Results: " + str(latency) + " ms (parsed message, round trip)" | |
737 self.on_receive(ping_msg,None) | |
738 if xml_dom: | |
739 xml_dom.unlink() | |
740 | |
741 def on_group(self, id, msg, xml_dom): | |
742 name = xml_dom.getAttribute("name") | |
743 players = xml_dom.getAttribute("players") | |
744 act = xml_dom.getAttribute("action") | |
745 pwd = xml_dom.getAttribute("pwd") | |
746 group_data = (id, name, pwd, players) | |
747 | |
748 if act == 'new': | |
749 self.groups[id] = group_data | |
750 self.on_group_event(mplay_event(GROUP_NEW, group_data)) | |
751 elif act == 'del': | |
752 del self.groups[id] | |
753 self.on_group_event(mplay_event(GROUP_DEL, group_data)) | |
754 elif act == 'update': | |
755 self.groups[id] = group_data | |
756 self.on_group_event(mplay_event(GROUP_UPDATE, group_data)) | |
757 if xml_dom: | |
758 xml_dom.unlink() | |
759 | |
760 def on_role(self, id, msg, xml_dom): | |
761 act = xml_dom.getAttribute("action") | |
762 role = xml_dom.getAttribute("role") | |
763 if (act == "set") or (act == "update"): | |
764 try: | |
765 (a,b,c,d,e,f,g,h) = self.players[id] | |
766 if id == self.id: | |
767 self.players[id] = (a,b,c,d,e,f,g,role) | |
768 self.update_role(role) | |
769 else: | |
770 self.players[id] = (a,b,c,d,e,f,g,role) | |
771 self.on_player_event(mplay_event(PLAYER_UPDATE,self.players[id])) | |
772 except: | |
773 pass | |
774 if xml_dom: | |
775 xml_dom.unlink() | |
776 | |
777 def on_player(self, id, msg, xml_dom): | |
778 act = xml_dom.getAttribute("action") | |
779 ip = xml_dom.getAttribute("ip") | |
780 name = xml_dom.getAttribute("name") | |
781 status = xml_dom.getAttribute("status") | |
782 version = xml_dom.getAttribute("version") | |
783 protocol_version = xml_dom.getAttribute("protocol_version") | |
784 client_string = xml_dom.getAttribute("client_string") | |
785 try: | |
786 player = (name,ip,id,status,version,protocol_version,client_string,self.players[id][7]) | |
787 except Exception, e: | |
788 player = (name,ip,id,status,version,protocol_version,client_string,"Player") | |
789 if act == "new": | |
790 self.players[id] = player | |
791 self.on_player_event(mplay_event(PLAYER_NEW,self.players[id])) | |
792 elif act == "group": | |
793 self.group_id = xml_dom.getAttribute("group_id") | |
794 self.clear_players() | |
795 self.on_mplay_event(mplay_event(MPLAY_GROUP_CHANGE,self.groups[self.group_id])) | |
796 self.players[self.id] = self.get_my_info() #(self.name,self.ip,self.id,self.text_status) | |
797 self.on_player_event(mplay_event(PLAYER_NEW,self.players[self.id])) | |
798 elif act == "failed": | |
799 self.on_mplay_event(mplay_event(MPLAY_GROUP_CHANGE_F)) | |
800 elif act == "del": | |
801 self.on_player_event(mplay_event(PLAYER_DEL,self.players[id])) | |
802 if self.players.has_key(id): | |
803 del self.players[id] | |
804 if id == self.id: | |
805 self.do_disconnect() | |
806 # the next two cases handle the events that are used to let you know when others are typing | |
807 elif act == "update": | |
808 if id == self.id: | |
809 self.players[id] = player | |
810 self.update_self_from_player(player) | |
811 else: | |
812 self.players[id] = player | |
813 dont_send = 0 | |
814 for m in self.ignore_id: | |
815 if m == id: | |
816 dont_send=1 | |
817 if dont_send != 1: | |
818 self.on_player_event(mplay_event(PLAYER_UPDATE,self.players[id])) | |
819 if xml_dom: | |
820 xml_dom.unlink() | |
821 | |
822 def on_password(self, id, msg, xml_dom): | |
823 signal = type = id = data = None | |
824 id = xml_dom.getAttribute("id") | |
825 type = xml_dom.getAttribute("type") | |
826 signal = xml_dom.getAttribute("signal") | |
827 data = xml_dom.getAttribute("data") | |
828 self.on_password_signal( signal,type,id,data ) | |
829 if xml_dom: | |
830 xml_dom.unlink() | |
831 | |
832 def check_my_status(self): | |
833 status = self.get_status() | |
834 if status == MPLAY_DISCONNECTING: | |
835 self.do_disconnect() | |
836 | |
837 def connect(self, addressport): | |
838 """Establish a connection to a server while still using sendThread & recvThread for its | |
839 communication.""" | |
840 if self.is_connected(): | |
841 self.log_msg( "Client is already connected to a server?!? Need to disconnect first." ) | |
842 return 0 | |
843 xml_dom = None | |
844 self.inbox = Queue.Queue(0) | |
845 self.outbox = Queue.Queue(0) | |
846 addressport_ar = addressport.split(":") | |
847 if len(addressport_ar) == 1: | |
848 address = addressport_ar[0] | |
849 port = OPENRPG_PORT | |
850 else: | |
851 address = addressport_ar[0] | |
852 port = int(addressport_ar[1]) | |
853 self.host_server = addressport | |
854 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
855 try: | |
856 self.sock.connect((address,port)) | |
857 # send client into with id=0 | |
858 self.sendMsg( self.sock, self.toxml("new") ) | |
859 data = self.recvMsg( self.sock ) | |
860 # get new id and group_id | |
861 xml_dom = parseXml(data) | |
862 xml_dom = xml_dom._get_documentElement() | |
863 self.id = xml_dom.getAttribute("id") | |
864 self.group_id = xml_dom.getAttribute("group_id") | |
865 if xml_dom.hasAttribute('useCompression') and xml_dom.getAttribute('useCompression') == 'True': | |
866 self.useCompression = True | |
867 if xml_dom.hasAttribute('cmpType'): | |
868 if cmpBZ2 and xml_dom.getAttribute('cmpType') == 'bz2': | |
869 self.compressionType = bz2 | |
870 elif cmpZLIB and xml_dom.getAttribute('cmpType') == 'zlib': | |
871 self.compressionType = zlib | |
872 else: | |
873 self.compressionType = None | |
874 else: | |
875 self.compressionType = bz2 | |
876 #send confirmation | |
877 self.sendMsg( self.sock, self.toxml("new") ) | |
878 except Exception, e: | |
879 self.log_msg(e) | |
880 if xml_dom: | |
881 xml_dom.unlink() | |
882 return 0 | |
883 | |
884 # Start things rollings along | |
885 self.initialize_threads() | |
886 self.on_mplay_event(mplay_event(MPLAY_CONNECTED)) | |
887 self.players[self.id] = (self.name,self.ip,self.id,self.text_status,self.version,self.protocol_version,self.client_string,self.role) | |
888 self.on_player_event(mplay_event(PLAYER_NEW,self.players[self.id])) | |
889 if xml_dom: | |
890 xml_dom.unlink() | |
891 return 1 | |
892 | |
893 def start_disconnect(self): | |
894 self.on_mplay_event(mplay_event(MPLAY_DISCONNECTING)) | |
895 self.outbox.put( self.toxml("del") ) | |
896 ## Client Side Disconect Forced -- Snowdog 10-09-2003 | |
897 #pause to allow GUI events time to sync. | |
898 time.sleep(1) | |
899 self.do_disconnect() | |
900 | |
901 def do_disconnect(self): | |
902 client_base.disconnect(self) | |
903 self.clear_players() | |
904 self.clear_groups() | |
905 self.useroles = 0 | |
906 self.on_mplay_event(mplay_event(MPLAY_DISCONNECTED)) | |
907 self.useCompression = False | |
908 | |
909 def is_connected(self): | |
910 return (self.status == MPLAY_CONNECTED) | |
911 | |
912 def get_next_id(self): | |
913 self.unique_cookie += 1 | |
914 return_str = self.id + "-" + str(self.unique_cookie) | |
915 return return_str |