root/projects/2008/pyogp/pyogp.lib.base/trunk/pyogp/lib/base/agent.py

Revision 2493, 41.8 kB (checked in by joshua.linden, 5 months ago)

* Use the PCode enum instead of magic numbers
* Watch out for "None" values when applying ObjectUpdates? to agent
* Add AgentDynamicsUpdate? app event
* Handle 48-, 32-, and 16-byte ObjectUpdates?
* Add basic sit/stand/fly methods to agent, with associated enums

Not reviewed

Line 
1 # standard python libs
2 from logging import getLogger, CRITICAL, ERROR, WARNING, INFO, DEBUG
3 import re
4 import sys
5 import signal
6 import uuid
7 import sets
8
9 #related
10 from eventlet import api
11
12 # pyogp
13 from pyogp.lib.base.login import Login, LegacyLoginParams, OGPLoginParams
14 from pyogp.lib.base.datatypes import *
15 from pyogp.lib.base.exc import LoginError
16 from pyogp.lib.base.region import Region
17 from pyogp.lib.base.inventory import *
18 from pyogp.lib.base.groups import *
19 from pyogp.lib.base.event_system import *
20 from pyogp.lib.base.appearance import *
21
22 # pyogp messaging
23 from pyogp.lib.base.message.message_handler import MessageHandler
24
25 from pyogp.lib.base.message.packets import *
26
27 # pyogp utilities
28 from pyogp.lib.base.utilities.helpers import Helpers
29 from pyogp.lib.base.utilities.enums import *
30
31
32 # initialize logging
33 logger = getLogger('pyogp.lib.base.agent')
34 log = logger.log
35
36 class Agent(object):
37     """ The Agent class is a container for agent specific data.
38
39     Example, of login via the agent class:
40     Initialize the login class
41
42     >>> client = Agent()
43     >>> client.login('https://login.agni.lindenlab.com/cgi-bin/login.cgi', 'firstname', 'lastname', 'secret', start_location = 'last')
44
45     Sample implementations: examples/sample_agent_login.py
46     Tests: tests/login.txt, tests/test_agent.py
47
48     """
49
50     def __init__(self, settings = None, firstname = '', lastname = '', password = '', agent_id = None, events_handler = None, handle_signals=True):
51         """ initialize this agent """
52
53         # allow the settings to be passed in
54         # otherwise, grab the defaults
55         if settings != None:
56             self.settings = settings
57         else:
58             from pyogp.lib.base.settings import Settings
59             self.settings = Settings()
60
61         # allow the eventhandler to be passed in
62         # so that applications running multiple avatars
63         # may use the same eventhandler
64
65         # otherwise, let's just use our own
66         if events_handler != None:
67             self.events_handler = events_handler
68         else:
69             self.events_handler = AppEventsHandler()
70
71         # signal handler to capture erm signals
72         if handle_signals:
73             self.signal_handler = signal.signal(signal.SIGINT, self.sigint_handler)
74
75         # storage containers for agent attributes
76         # we overwrite with what the grid tells us, rather than what
77         # is passed in and stored in Login()
78         self.firstname = firstname
79         self.lastname = lastname
80         self.password = password
81         self.agent_id = None
82         self.session_id = None
83         self.secure_session_id = None
84         self.name = self.Name()
85
86         # other storage containers
87         self.inventory_host = None
88         self.agent_access = None
89         self.udp_blacklist = None
90         self.home = None
91         self.inventory = None
92         self.start_location = None
93         self.group_manager = GroupManager(self, self.settings)
94
95
96         # additional attributes
97         self.login_response = None
98         self.connected = False
99         self.grid_type = None
100         self.running = True
101         self.helpers = Helpers()
102
103         # data we store as it comes in from the grid
104         self.Position = Vector3()     # this will get updated later, but seed it with 000
105         self.LookAt = Vector3()
106         self.ActiveGroupID = UUID()
107
108         # populated via ObjectUpdates
109         self.FootCollisionPlane = Quaternion()
110         self.Velocity = Vector3()
111         self.Acceleration = Vector3()
112         self.Rotation = Vector3()
113         self.AngularVelocity = Vector3()
114        
115         # movement
116         self.state = AgentState.Null # typing, editing
117         self.control_flags = 0
118         self.agent_update_flags = AgentUpdateFlags.Null
119
120
121         # should we include these here?
122         self.agentdomain = None     # the agent domain the agent is connected to if an OGP context
123         self.child_regions = []     # all neighboring regions
124         self._pending_child_regions = []    # neighbor regions an agent may connect to
125         self.region = None          # the host simulation for the agent
126
127         # init AppearanceManager()
128         self.appearance = AppearanceManager(self.settings, self)
129
130         # Cache of region name->handle; per-agent to prevent information leaks
131         self.region_name_map = {}
132
133         # Cache of agent_id->(first_name, last_name); per agent to prevent info leaks
134         self.agent_id_map = {}
135
136         if self.settings.LOG_VERBOSE: log(DEBUG, 'Initializing agent: %s' % (self))
137
138     def Name(self):
139         """ returns a concatenated firstname + ' ' + lastname"""
140
141         return self.firstname + ' ' + self.lastname
142
143     def login(self, loginuri, firstname=None, lastname=None, password=None, login_params = None, start_location=None, handler=None, connect_region = True):
144         """ login to a login endpoint using the Login() class """
145
146         if (re.search('auth.cgi$', loginuri)):
147
148             self.grid_type = 'OGP'
149
150         elif (re.search('login.cgi$', loginuri)):
151
152             self.grid_type = 'Legacy'
153
154         else:
155             log(WARNING, 'Unable to identify the loginuri schema. Stopping')
156             sys.exit(-1)
157
158         if firstname != None:
159             self.firstname = firstname
160         if lastname != None:
161             self.lastname = lastname
162         if password != None:
163             self.password = password
164
165         # handle either login params passed in, or, account info
166         if login_params == None:
167
168             if (self.firstname == '') or (self.lastname == '') or (self.password == ''):
169
170                 raise LoginError('Unable to login an unknown agent.')
171
172             else:
173
174                 self._login_params = self._get_login_params(loginuri, self.firstname, self.lastname, self.password)
175
176         else:
177
178             self._login_params = login_params
179
180         # login and parse the response
181         login = Login(settings = self.settings)
182
183         self.login_response = login.login(loginuri, self._login_params, start_location, handler = handler)
184         self._parse_login_response()
185
186         # ToDo: what to do with self.login_response['look_at']?
187
188         if self.settings.MULTIPLE_SIM_CONNECTIONS:
189             api.spawn(self._monitor_for_new_regions)
190
191         if connect_region:
192             self._enable_current_region()
193                              
194
195     def logout(self):
196         """ logs an agent out of the current region. calls Region()._kill_coroutines() for all child regions, and Region().logout() for the host region """
197
198         if not self.connected:
199             log(INFO, 'Agent is not logged into the grid. Stopping.')
200             sys.exit()
201
202         self.running = False
203
204         if self.region == None:
205             return
206         else:
207
208             # kill udp and or event queue for child regions
209             [region._kill_coroutines() for region in self.child_regions]
210
211             if self.region.logout():
212                 self.connected = False
213
214         # zero out the password in case we dump it somewhere
215         self.password = ''
216
217     def _get_login_params(self, loginuri, firstname, lastname, password):
218         """ get the proper login parameters of the legacy or ogp enabled grid """
219
220         if self.grid_type == 'OGP':
221
222             login_params = OGPLoginParams(firstname, lastname, password)
223
224         elif self.grid_type == 'Legacy':
225
226             login_params = LegacyLoginParams(firstname, lastname, password)
227
228         return login_params
229
230     def _parse_login_response(self):
231         """ evaluates the login response and propagates data to the Agent() attributes. enables InventoryManager() if settings dictate """
232
233         if self.grid_type == 'Legacy':
234
235             self.firstname = re.sub(r'\"', '', self.login_response['first_name'])
236             self.lastname = self.login_response['last_name']
237             self.agent_id = UUID(self.login_response['agent_id'])
238             self.session_id = UUID(self.login_response['session_id'])
239             self.secure_session_id = UUID(self.login_response['secure_session_id'])
240
241             self.connected = bool(self.login_response['login'])
242             self.inventory_host = self.login_response['inventory_host']
243             self.agent_access = self.login_response['agent_access']
244             self.udp_blacklist = self.login_response['udp_blacklist']
245             self.start_location = self.login_response['start_location']
246
247             if self.login_response.has_key('home'): self.home = Home(self.login_response['home'])
248
249         elif self.grid_type == 'OGP':
250
251             pass
252
253     def _enable_current_region(self, region_x = None, region_y = None, seed_capability = None, udp_blacklist = None, sim_ip = None, sim_port = None, circuit_code = None):
254         """ enables and connects udp and event queue for an agent's current region """
255
256         if self.login_response.has_key('circuit_code'):
257             self.circuit_code = self.login_response['circuit_code']
258
259         region_x = region_x or self.login_response['region_x']
260         region_y = region_y or self.login_response['region_y']
261         seed_capability = seed_capability or self.login_response['seed_capability']
262         udp_blacklist = udp_blacklist or self.login_response['udp_blacklist']
263         sim_ip = sim_ip or self.login_response['sim_ip']
264         sim_port = sim_port or self.login_response['sim_port']
265         circuit_code = circuit_code or self.login_response['circuit_code']
266
267         # enable the current region, setting connect = True
268         self.region = Region(region_x, region_y, seed_capability, udp_blacklist, sim_ip, sim_port, circuit_code, self, settings = self.settings, events_handler = self.events_handler)
269
270         self.region.is_host_region = True       
271
272         # start the simulator udp and event queue connections
273         if self.settings.LOG_COROUTINE_SPAWNS: log(INFO, "Spawning a coroutine for connecting to the agent's host region.")
274
275         api.spawn(self.region.connect)
276        
277         self.enable_callbacks()
278
279        
280
281     def _enable_child_region(self, region_params):
282         """ enables a child region. eligible simulators are sent in EnableSimulator over the event queue, and routed through the packet handler """
283
284         # if this is the sim we are already connected to, skip it
285         if self.region.sim_ip == region_params['IP'] and self.region.sim_port == region_params['Port']:
286             #self.region.sendCompleteAgentMovement()
287             log(DEBUG, "Not enabling a region we are already connected to: %s" % (str(region_params['IP']) + ":" + str(region_params['Port'])))
288             return
289
290         child_region = Region(circuit_code = self.circuit_code, sim_ip = region_params['IP'], sim_port = region_params['Port'], handle = region_params['Handle'], agent = self, settings = self.settings, events_handler = self.events_handler)
291
292         self.child_regions.append(child_region)
293
294         log(INFO, "Enabling a child region with ip:port of %s" % (str(region_params['IP']) + ":" + str(region_params['Port'])))
295
296         if self.settings.LOG_COROUTINE_SPAWNS: log(INFO, "Spawning a coroutine for connecting to a neighboring region.")
297
298         api.spawn(child_region.connect_child)
299
300     def _monitor_for_new_regions(self):
301         """ enable connections to neighboring regions found in the pending queue """
302
303         while self.running:
304
305             if len(self._pending_child_regions) > 0:
306
307                 for region_params in self._pending_child_regions:
308                    
309                     self._enable_child_region(region_params)
310                     self._pending_child_regions.remove(region_params)
311
312             api.sleep(0)
313
314     def _start_EQ_on_neighboring_region(self, message):
315         """ enables the event queue on an agent's neighboring region """
316
317         region = [region for region in self.child_regions if message.blocks['Message_Data'][0].get_variable('sim-ip-and-port').data == str(region.sim_ip) + ":" + str(region.sim_port)]
318
319         if region != []:
320
321             region[0]._set_seed_capability(message.blocks['Message_Data'][0].get_variable('seed-capability').data)
322
323             region[0]._get_region_capabilities()
324
325             log(DEBUG, 'Spawning neighboring region event queue connection')
326             region[0]._startEventQueue()
327
328     def enable_callbacks(self):
329         """ enable the Agents() callback handlers for packet received events """
330         # TODO oopify
331        
332         if self.settings.ENABLE_INVENTORY_MANAGEMENT:
333             while self.region.capabilities == {}:
334
335                 api.sleep(0)
336            
337             inventory_caps = ['FetchInventory', 'WebFetchInventoryDescendents', 'FetchLib', 'FetchLibDescendents']
338    
339             if sets.Set(self.region.capabilities.keys()).intersection(inventory_caps):
340    
341                 caps = dict([(capname, self.region.capabilities[capname]) for capname in inventory_caps])
342    
343                 log(INFO, "Using the capability based inventory management mechanism")
344    
345                 self.inventory = AIS(self, caps)
346    
347             else:
348    
349                 log(INFO, "Using the UDP based inventory management mechanism")
350    
351                 self.inventory = UDP_Inventory(self)
352    
353             self.inventory._parse_folders_from_login_response()   
354             self.inventory.enable_callbacks()
355
356         if self.settings.ENABLE_APPEARANCE_MANAGEMENT:
357             self.appearance.enable_callbacks()
358         if self.settings.ENABLE_GROUP_CHAT:
359             self.group_manager.enable_callbacks()
360         if self.settings.MULTIPLE_SIM_CONNECTIONS:
361
362             onEnableSimulator_received = self.region.message_handler.register('EnableSimulator')
363             onEnableSimulator_received.subscribe(self.onEnableSimulator)
364
365             onEstablishAgentCommunication_received = self.region.message_handler.register('EstablishAgentCommunication')
366             onEstablishAgentCommunication_received.subscribe(self.onEstablishAgentCommunication)
367
368         if self.settings.HANDLE_PACKETS:
369
370             onAlertMessage_received = self.region.message_handler.register('AlertMessage')
371             onAlertMessage_received.subscribe(self.onAlertMessage)
372
373             onAgentDataUpdate_received = self.region.message_handler.register('AgentDataUpdate')
374             onAgentDataUpdate_received.subscribe(self.onAgentDataUpdate)
375
376             onAgentMovementComplete_received = self.region.message_handler.register('AgentMovementComplete')
377             onAgentMovementComplete_received.subscribe(self.onAgentMovementComplete)
378
379             onHealthMessage_received = self.region.message_handler.register('HealthMessage')
380             onHealthMessage_received.subscribe(self.onHealthMessage)
381
382             onImprovedInstantMessage_received = self.region.message_handler.register('ImprovedInstantMessage')
383             onImprovedInstantMessage_received.subscribe(self.onImprovedInstantMessage)
384
385             self.region.message_handler.register('TeleportStart').subscribe(self.simple_callback('Info'))
386             self.region.message_handler.register('TeleportProgress').subscribe(self.simple_callback('Info'))
387             self.region.message_handler.register('TeleportFailed').subscribe(self.simple_callback('Info'))
388             self.region.message_handler.register('TeleportFinish').subscribe(self.onTeleportFinish)
389
390             self.region.message_handler.register('OfflineNotification').subscribe(self.simple_callback('AgentBlock'))
391             self.region.message_handler.register('OnlineNotification').subscribe(self.simple_callback('AgentBlock'))
392
393             self.region.message_handler.register('MoneyBalanceReply').subscribe(self.simple_callback('MoneyData'))
394             self.region.message_handler.register('RoutedMoneyBalanceReply').subscribe(self.simple_callback('MoneyData'))
395
396             if self.settings.ENABLE_COMMUNICATIONS_TRACKING:
397                 onChatFromSimulator_received = self.region.message_handler.register('ChatFromSimulator')
398                 onChatFromSimulator_received.subscribe(self.onChatFromSimulator)
399    
400
401     def simple_callback(self, blockname):
402         """Generic callback creator for single-block packets."""
403
404         def repack(packet, blockname):
405             """Repack a single block packet into an AppEvent"""
406             payload = {}
407             block = packet.blocks[blockname][0]
408             for var in block.var_list:
409                 payload[var] = block.get_variable(var).data
410
411             return AppEvent(packet.name, payload=payload)
412
413         return lambda p: self.events_handler._handle(repack(p, blockname))
414
415
416     def send_AgentDataUpdateRequest(self):
417         """ queues a packet requesting an agent data update """
418
419         packet = AgentDataUpdateRequestPacket()
420
421         packet.AgentData['AgentID'] = self.agent_id
422         packet.AgentData['SessionID'] = self.session_id
423
424         self.region.enqueue_message(packet())
425
426     # ~~~~~~~~~~~~~~
427     # Communications
428     # ~~~~~~~~~~~~~~
429
430     # Chat
431
432     def say(self, Message, Type = 1, Channel = 0):
433         """ queues a packet to send open chat via ChatFromViewer
434
435         Channel: 0 is open chat
436         Type: 0 = Whisper
437               1 = Say
438               2 = Shout
439         """
440
441         packet = ChatFromViewerPacket()
442
443         packet.AgentData['AgentID'] = self.agent_id
444         packet.AgentData['SessionID'] = self.session_id
445
446         packet.ChatData['Message'] = Message
447         packet.ChatData['Type'] = Type
448         packet.ChatData['Channel'] = Channel
449
450         self.region.enqueue_message(packet())
451
452     # Instant Message (im, group chat)
453
454     def instant_message(self, ToAgentID = None, Message = None, _ID = None):
455         """ sends an instant message to another avatar, wrapping Agent().send_ImprovedInstantMessage() with some handy defaults """
456
457         if ToAgentID != None and Message != None:
458
459             if _ID == None: _ID = self.agent_id
460
461             _AgentID = self.agent_id
462             _SessionID = self.session_id
463             _FromGroup = False
464             _ToAgentID = UUID(str(ToAgentID))
465             _ParentEstateID = 0
466             _RegionID = UUID()
467             _Position = self.Position
468             _Offline = 0
469             _Dialog = ImprovedIMDialogue.FromAgent
470             _ID = _ID
471             _Timestamp = 0
472             _FromAgentName = self.firstname + ' ' + self.lastname
473             _Message = Message
474             _BinaryBucket = ''
475
476             self.send_ImprovedInstantMessage(_AgentID, _SessionID, _FromGroup, _ToAgentID, _ParentEstateID, _RegionID, _Position, _Offline, _Dialog, _ID, _Timestamp, _FromAgentName, _Message, _BinaryBucket)
477
478         else:
479
480             log(INFO, "Please specify an agentid and message to send in agent.instant_message")
481
482     def send_ImprovedInstantMessage(self, AgentID = None, SessionID = None, FromGroup = None, ToAgentID = None, ParentEstateID = None, RegionID = None, Position = None, Offline = None, Dialog = None, _ID = None, Timestamp = None, FromAgentName = None, Message = None, BinaryBucket = None, AgentDataBlock = {}, MessageBlockBlock = {}):
483         """ sends an instant message packet to ToAgentID. this is a multi-purpose message for inventory offer handling, im, group chat, and more """
484
485         packet = ImprovedInstantMessagePacket()
486
487         if AgentDataBlock == {}:
488             packet.AgentData['AgentID'] = AgentID
489             packet.AgentData['SessionID'] = SessionID           
490         else:
491             packet.AgentData = AgentDataBlock
492
493         if FromAgentName == None:
494             FromAgentName = self.Name()
495
496         # ha! when scripting out packets.py, never considered a block named *block
497         if MessageBlockBlock == {}:
498
499             packet.MessageBlock['FromGroup'] = FromGroup                   # Bool
500             packet.MessageBlock['ToAgentID'] = UUID(str(ToAgentID))   # LLUUID
501             packet.MessageBlock['ParentEstateID'] = ParentEstateID         # U32
502             packet.MessageBlock['RegionID'] = UUID(str(RegionID))     # LLUUID
503             packet.MessageBlock['Position'] = Position()                     # LLVector3
504             packet.MessageBlock['Offline'] = Offline                       # U8
505             packet.MessageBlock['Dialog'] = Dialog                         # U8 IM Type
506             packet.MessageBlock['ID'] = UUID(str(_ID))                # LLUUID
507             packet.MessageBlock['Timestamp'] = Timestamp                   # U32
508             packet.MessageBlock['FromAgentName'] = FromAgentName           # Variable 1
509             packet.MessageBlock['Message'] = Message                       # Variable 2
510             packet.MessageBlock['BinaryBucket'] = BinaryBucket             # Variable 2
511
512         self.region.enqueue_message(packet(), True)
513  
514     def send_RetrieveInstantMessages(self):
515         """ asks simulator for instant messages stored while agent was offline """
516        
517         packet = RetrieveInstantMessagesPackets()
518        
519         packet.AgentDataBlock['AgentID'] = self.agent_id
520         packet.AgentDataBlock['SessionID'] = self.session_id
521        
522         self.region.enqueue_message(packet())
523
524
525     def sigint_handler(self, signal, frame):
526         """ catches terminal signals (Ctrl-C) to kill running client instances """
527
528         log(INFO, "Caught signal... %d. Stopping" % signal)
529         #self.running = False
530         self.logout()
531         #sys.exit(0)
532
533     def __repr__(self):
534         """ returns a representation of the agent """
535
536         if self.firstname == None:
537             return 'A new agent instance'
538         else:
539             return self.Name()
540
541     def onAgentDataUpdate(self, packet):
542         """ callback handler for received AgentDataUpdate messages which populates various Agent() attributes """
543
544         if self.agent_id == None:
545             self.agent_id = packet.blocks['AgentData'][0].get_variable('AgentID').data
546
547         if self.firstname == None:
548             self.firstname = packet.blocks['AgentData'][0].get_variable('FirstName').data
549
550         if self.lastname == None:
551             self.firstname = packet.blocks['AgentData'][0].get_variable('LastName').data
552
553         self.GroupTitle = packet.blocks['AgentData'][0].get_variable('GroupTitle').data
554
555         self.ActiveGroupID = packet.blocks['AgentData'][0].get_variable('ActiveGroupID').data
556
557         self.GroupPowers = packet.blocks['AgentData'][0].get_variable('GroupPowers').data
558
559         self.GroupName = packet.blocks['AgentData'][0].get_variable('GroupName').data
560
561     def onAgentMovementComplete(self, packet):
562         """ callback handler for received AgentMovementComplete messages which populates various Agent() and Region() attributes """
563
564         self.Position = packet.blocks['Data'][0].get_variable('Position').data
565         if self.Position == None:
566             log(WARNING, "agent.position is None agent.py")
567         self.LookAt = packet.blocks['Data'][0].get_variable('LookAt').data
568
569         self.region.RegionHandle = packet.blocks['Data'][0].get_variable('RegionHandle').data
570
571         #agent.Timestamp = packet.blocks['Data'][0].get_variable('Timestamp')
572
573         self.region.ChannelVersion = packet.blocks['SimData'][0].get_variable('ChannelVersion').data
574
575         # Raise a plain-vanilla AppEvent
576         self.simple_callback('Data')(packet)
577
578     def sendDynamicsUpdate(self):
579         """Called when an ObjectUpdate is received for the agent; raises
580         an app event."""
581         payload = {}
582         payload['Position'] = self.Position
583         payload['FootCollisionPlane'] = self.FootCollisionPlane
584         payload['Velocity'] = self.Velocity
585         payload['Acceleration'] = self.Acceleration
586         payload['Rotation'] = self.Rotation
587         payload['AngularVelocity'] = self.AngularVelocity
588         self.events_handler._handle(AppEvent("AgentDynamicsUpdate", payload=payload))
589
590     def onHealthMessage(self, packet):
591         """ callback handler for received HealthMessage messages which populates Agent().health """
592
593         self.health = packet.blocks['HealthData'][0].get_variable('Health').data
594
595     def onAgentGroupDataUpdate(self, packet):
596         """ callback handler for received AgentGroupDataUpdate messages which updates stored group instances in the group_manager """
597
598         # AgentData block
599         AgentID = packet.blocks['AgentData'][0].get_variable('AgentID').data
600
601         # GroupData block
602         for GroupData_block in packet.blocks['GroupData']:
603
604             AcceptNotices = GroupData_block.get_variable('AcceptNotices').data
605             GroupPowers = GroupData_block.get_variable('GroupPowers').data
606             GroupID = GroupData_block.get_variable('GroupID').data
607             GroupName = GroupData_block.get_variable('GroupName').data
608             ListInProfile = GroupData_block.get_variable('ListInProfile').data
609             Contribution = GroupData_block.get_variable('Contribution').data
610             GroupInsigniaID = GroupData_block.get_variable('GroupInsigniaID').data
611
612             # make sense of group powers
613             GroupPowers = [ord(x) for x in GroupPowers]
614             GroupPowers = ''.join([str(x) for x in GroupPowers])
615
616             group = Group(AcceptNotices, GroupPowers, GroupID, GroupName, ListInProfile, Contribution,GroupInsigniaID )
617
618             self.group_manager.store_group(group)
619
620     def onChatFromSimulator(self, packet):
621         """ callback handler for received ChatFromSimulator messages which parses and fires a ChatReceived event. """
622
623         log(INFO, "Working on parsing chat messages....")
624
625         FromName = packet.blocks['ChatData'][0].get_variable('FromName').data
626         SourceID = packet.blocks['ChatData'][0].get_variable('SourceID').data
627         OwnerID = packet.blocks['ChatData'][0].get_variable('OwnerID').data
628         SourceType = packet.blocks['ChatData'][0].get_variable('SourceType').data
629         ChatType = packet.blocks['ChatData'][0].get_variable('ChatType').data
630         Audible = packet.blocks['ChatData'][0].get_variable('Audible').data
631         Position = packet.blocks['ChatData'][0].get_variable('Position').data
632         Message = packet.blocks['ChatData'][0].get_variable('Message').data
633
634         message = AppEvent('ChatReceived', FromName = FromName, SourceID = SourceID, OwnerID = OwnerID, SourceType = SourceType, ChatType = ChatType, Audible = Audible, Position = Position, Message = Message)
635
636         log(INFO, "Received chat from %s: %s" % (FromName, Message))
637
638         self.events_handler._handle(message)
639
640     def onImprovedInstantMessage(self, packet):
641         """ callback handler for received ImprovedInstantMessage messages. much is passed in this message, and handling the data is only partially implemented """
642
643         log(INFO, "Working on parsing ImprovedInstantMessage messages....")
644
645         Dialog = packet.blocks['MessageBlock'][0].get_variable('Dialog').data
646         FromAgentID = packet.blocks['AgentData'][0].get_variable('AgentID').data
647
648         if Dialog == ImprovedIMDialogue.InventoryOffered:
649
650             self.inventory.handle_inventory_offer(packet)
651
652         elif Dialog == ImprovedIMDialogue.InventoryAccepted:
653
654             if str(FromAgentID) != str(self.agent_id):
655
656                 FromAgentName = packet.blocks['MessageBlock'][0].get_variable('FromAgentName').data
657                 InventoryName = packet.blocks['MessageBlock'][0].get_variable('Message').data
658
659                 log(INFO, "Agent %s accepted the inventory offer." % (FromAgentName))
660
661         elif Dialog == ImprovedIMDialogue.InventoryDeclined:
662
663             if str(FromAgentID) != str(self.agent_id):
664
665                 FromAgentName = packet.blocks['MessageBlock'][0].get_variable('FromAgentName').data
666                 InventoryName = packet.blocks['MessageBlock'][0].get_variable('Message').data
667
668                 log(INFO, "Agent %s declined the inventory offer." % (FromAgentName))
669
670         elif Dialog == ImprovedIMDialogue.FromAgent:
671
672             RegionID = packet.blocks['MessageBlock'][0].get_variable('RegionID').data
673             Position = packet.blocks['MessageBlock'][0].get_variable('Position').data
674             ID = packet.blocks['MessageBlock'][0].get_variable('ID').data
675             FromAgentName = packet.blocks['MessageBlock'][0].get_variable('FromAgentName').data
676             Message = packet.blocks['MessageBlock'][0].get_variable('Message').data
677
678             message = AppEvent('InstantMessageReceived', FromAgentID = FromAgentID, RegionID = RegionID, Position = Position, ID = ID, FromAgentName = FromAgentName, Message = Message)
679
680             log(INFO, "Received instant message from %s: %s" % (FromAgentName, Message))
681
682             self.events_handler._handle(message)
683
684         else:
685
686             self.helpers.log_packet(packet, self)
687
688     def onAlertMessage(self, packet):
689         """ callback handler for received AlertMessage messages. logs and raises an event """
690
691         # ToDo: raise an event when this is received
692
693         AlertMessage = packet.blocks['AlertData'][0].get_variable('Message').data
694
695         log(WARNING, "AlertMessage from simulator: %s" % (AlertMessage))
696
697     def onEnableSimulator(self, packet):
698         """ callback handler for received EnableSimulator messages. stores the region data for later connections """
699
700         IP = [ord(x) for x in packet.blocks['SimulatorInfo'][0].get_variable('IP').data]
701         IP = '.'.join([str(x) for x in IP])
702
703         Port = packet.blocks['SimulatorInfo'][0].get_variable('Port').data
704
705         # not sure what this is, but pass it up
706         Handle = [ord(x) for x in packet.blocks['SimulatorInfo'][0].get_variable('Handle').data]
707
708         region_params = {'IP': IP, 'Port': Port, 'Handle': Handle}
709
710         log(INFO, 'Received EnableSimulator for %s' % (str(IP) + ":" + str(Port)))
711
712         # are we already prepping to connect to the sim?
713         if region_params not in self._pending_child_regions:
714
715             # are we already connected to the sim?
716             known_region = False
717
718             # don't append to the list if we already know about this region
719             for region in self.child_regions:
720                 if region.sim_ip == region_params['IP'] and region.sim_port == region_params['Port']:
721                     known_region = True
722
723             #agent._enable_child_region(IP, Port, Handle)
724             if not known_region:
725                 self._pending_child_regions.append(region_params)
726
727     def onEstablishAgentCommunication(self, message):
728         """ callback handler for received EstablishAgentCommunication messages. try to enable the event queue for a neighboring region based on the data received """
729
730         log(INFO, 'Received EstablishAgentCommunication for %s' % (message.blocks['Message_Data'][0].get_variable('sim-ip-and-port').data))
731
732         is_running = False
733
734         # don't enable the event queue when we already have it running
735         for region in self.child_regions:
736             if (str(region.sim_ip) + ":" + str(region.sim_port) == message.blocks['Message_Data'][0].get_variable('sim-ip-and-port').data) and region.event_queue != None:
737                 if region.event_queue._running:
738                     is_running = True
739
740         # start the event queue
741         if not is_running:
742             self._start_EQ_on_neighboring_region(message)
743
744
745     def teleport(self,
746                  region_name=None,
747                  region_handle=None,
748                  region_id=None,
749                  position=Vector3(X=128, Y=128, Z=128),
750                  look_at=Vector3(X=128, Y=128, Z=128)):
751         """Initiate a teleport to the specified location. When passing a region name
752         it may be necessary to request the destination region handle from the current sim
753         before the teleport can start."""
754        
755         log(INFO, 'teleport name=%s handle=%s id=%s', str(region_name), str(region_handle), str(region_id))
756        
757         # Handle intra-region teleports even by name
758         if not region_id and region_name and region_name.lower() == self.region.SimName.lower():
759             region_id = self.region.RegionID
760
761         if not region_id and not region_handle and region_name and \
762                region_name.lower() in self.region_name_map:
763             region_handle = self.region_name_map[region_name.lower()]
764
765         if region_id:
766             log(INFO, 'sending TP request packet')
767             packet = TeleportRequestPacket()
768
769             packet.AgentData['AgentID'] = self.agent_id
770             packet.AgentData['SessionID'] = self.session_id
771            
772             packet.Info['RegionID'] = region_id
773             packet.Info['Position'] = position
774             packet.Info['LookAt'] = look_at
775            
776             self.region.enqueue_message(packet())
777
778         elif region_handle:
779             log(INFO, 'sending TP location request packet')
780             packet = TeleportLocationRequestPacket()
781
782             packet.AgentData['AgentID'] = self.agent_id
783             packet.AgentData['SessionID'] = self.session_id
784            
785             packet.Info['RegionHandle'] = region_handle
786             packet.Info['Position'] = position
787             packet.Info['LookAt'] = look_at
788            
789             self.region.enqueue_message(packet())
790
791         else:
792             log(INFO, "Target region's handle not known, sending map name request")
793             # do a region_name to region_id lookup and then request the teleport
794             self.send_MapNameRequest(
795                 region_name,
796                 lambda handle: self.teleport(region_handle=handle, position=position, look_at=look_at))
797                
798    
799     def send_MapNameRequest(self, region_name, callback):
800
801         handler = self.region.message_handler.register('MapBlockReply')
802        
803         def onMapBlockReplyPacket(packet):
804             log(INFO, 'MapBlockReplyPacket received')
805             for block in packet.blocks['Data']:
806                 if block.get_variable('Name').data.lower() == region_name.lower():
807                     handler.unsubscribe(onMapBlockReplyPacket)
808
809                     x = block.get_variable('X').data
810                     y = block.get_variable('Y').data
811                     region_handle = Region.xy_to_handle(x,y)
812
813                     if region_handle:
814                         self.region_name_map[region_name.lower()] = region_handle
815                         callback(region_handle)
816                     else:
817                         # *TODO: May get a region_handle of 0 if region
818                         # is offline/unknown. Should callback handle it?
819                         log(WARNING, 'Got null region_handle for %s', region_name)
820                     return
821
822             # Leave it registered, as the event may come later
823            
824         # Register a handler for the response       
825         handler.subscribe(onMapBlockReplyPacket)
826
827         # ...and make the request
828         log(INFO, 'sending MapNameRequestPacket')
829         packet = MapNameRequestPacket()
830         packet.AgentData['AgentID'] = self.agent_id
831         packet.AgentData['SessionID'] = self.session_id
832         packet.AgentData['Flags'] = 0
833         packet.AgentData['EstateID'] = 0
834         packet.AgentData['Godlike'] = False
835         packet.NameData['Name'] = region_name.lower()
836         self.region.enqueue_message(packet())
837
838     def onTeleportFinish(self, packet):
839         """Handle the end of a successful teleport"""
840
841         log(INFO, "Teleport finished, taking care of details...")
842
843         # Raise a plain-vanilla AppEvent for the Info block
844         self.simple_callback('Info')(packet)
845
846         # packed binary U64 to integral x, y
847         region_handle = packet.blocks['Info'][0].get_variable('RegionHandle').data       
848         region_x, region_y = Region.handle_to_xy(region_handle)
849
850         # packed binary to dotted-octet
851         sim_ip = packet.blocks['Info'][0].get_variable('SimIP').data
852         sim_ip = '.'.join(map(str,struct.unpack('BBBB', sim_ip)))
853
854         # *TODO: Make this more graceful
855         log(INFO, "Disconnecting from old region")
856         [region._kill_coroutines() for region in self.child_regions]
857         self.region._kill_coroutines()
858
859         self.region = None
860         self.child_regions = []
861         self._pending_child_regions = []
862
863         log(INFO, "Enabling new region")
864         self._enable_current_region(
865             region_x = region_x,
866             region_y = region_y,
867             seed_capability = packet.blocks['Info'][0].get_variable('SeedCapability').data,
868             sim_ip = sim_ip,
869             sim_port = packet.blocks['Info'][0].get_variable('SimPort').data
870             )
871
872
873     def request_agent_names(self, agent_ids, callback):
874         """Request agent names. When all names are known, callback
875         will be called with a list of tuples (agent_id, first_name,
876         last_name). If all names are known, callback will be called
877         immediately."""
878        
879         def _fire_callback(_):
880             cbdata = [(agent_id,
881                        self.agent_id_map[agent_id][0],
882                        self.agent_id_map[agent_id][1])
883                       for agent_id in agent_ids]
884             callback(cbdata)
885        
886         names_to_request = [ agent_id
887                              for agent_id in agent_ids
888                              if agent_id not in self.agent_id_map ]
889         if names_to_request:
890             self.send_UUIDNameRequest(names_to_request, _fire_callback)
891         else:
892             _fire_callback([])           
893
894
895     def send_UUIDNameRequest(self, agent_ids, callback):
896         handler = self.region.message_handler.register('UUIDNameReply')
897
898         def onUUIDNameReply(packet):
899             log(INFO, 'UUIDNameReplyPacket received')
900            
901             cbdata = []
902             for block in packet.blocks['UUIDNameBlock']:
903                 agent_id = str(block.get_variable('ID').data)
904                 first_name = block.get_variable('FirstName').data
905                 last_name = block.get_variable('LastName').data
906                 self.agent_id_map[agent_id] = (first_name, last_name)
907                 cbdata.append((agent_id, first_name, last_name))
908
909             # Fire the callback only when all names are received
910             missing = [ agent_id
911                         for agent_id in agent_ids
912                         if agent_id not in self.agent_id_map ]
913             if not missing:
914                 handler.unsubscribe(onUUIDNameReply)
915                 callback(cbdata)
916             else:
917                 log(INFO, 'Still waiting on %d names', len(missing))
918            
919         handler.subscribe(onUUIDNameReply)
920         log(INFO, 'sending UUIDNameRequest')
921         packet = UUIDNameRequestPacket()
922         packet.UUIDNameBlockBlocks = [ {'ID':UUID(agent_id) } for agent_id in agent_ids ]
923         self.region.enqueue_message(packet())
924
925         # *TODO: Should use this instead, but somehow it fails ?!?
926         #self.region.sendUUIDNameRequest(agent_ids=agent_ids)
927
928     def request_balance(self, callback):
929         """Request the current agent balance."""
930         handler = self.region.message_handler.register('MoneyBalanceReply')
931
932         def onMoneyBalanceReply(packet):
933             log(INFO, 'MoneyBalanceReply received')
934             handler.unsubscribe(onMoneyBalanceReply) # One-shot handler
935             balance = packet.blocks['MoneyData'][0].get_variable('MoneyBalance').data
936             callback(balance)
937
938         handler.subscribe(onMoneyBalanceReply)
939         log(INFO, 'sending MoneyBalanceRequest')
940         packet = MoneyBalanceRequestPacket()
941         packet.AgentData['AgentID'] = self.agent_id
942         packet.AgentData['SessionID'] = self.session_id
943         packet.MoneyData['TransactionID'] = UUID()
944         self.region.enqueue_message(packet())
945        
946
947     def give_money(self, target_id, amount,
948                    description='',
949                    transaction_type=MoneyTransactionType.Gift,
950                    flags=TransactionFlags.Null):
951         """Give money to another agent"""
952         log(INFO, 'sending MoneyTransferRequest')
953         packet = MoneyTransferRequestPacket()
954         packet.AgentData['AgentID'] = self.agent_id
955         packet.AgentData['SessionID'] = self.session_id
956         packet.MoneyData['SourceID'] = self.agent_id
957         packet.MoneyData['DestID'] = UUID(target_id)
958         packet.MoneyData['Flags'] = flags
959         packet.MoneyData['Amount'] = amount
960         packet.MoneyData['AggregatePermNextOwner'] = 0
961         packet.MoneyData['AggregatePermInventory'] = 0
962         packet.MoneyData['TransactionType'] = transaction_type
963         packet.MoneyData['Description'] = description
964         self.region.enqueue_message(packet())
965
966
967     def sit_on_ground(self):
968         """Sit on the ground at the agent's current location"""
969         self.control_flags |= AgentControlFlags.SitOnGround
970         self._send_update()
971
972     def stand(self):
973         """Stand up from sitting"""
974         # Start standing...
975         self.control_flags &= ~AgentControlFlags.SitOnGround
976         self.control_flags |= AgentControlFlags.StandUp
977         self._send_update()
978         # And finish standing
979         self.control_flags &= ~AgentControlFlags.StandUp
980         self._send_update()
981
982
983     def fly(self, flying=True):
984         """Start or stop flying"""
985         if flying:
986             self.control_flags |= AgentControlFlags.StandUp
987         else:           
988             self.control_flags &= ~AgentControlFlags.StandUp
989         self._send_update()
990
991     def _send_update(self):
992         log(INFO, 'sending AgentUpdate')
993         self.region.sendAgentUpdate(
994             State=self.state,
995             ControlFlags=self.control_flags,
996             Flags=self.agent_update_flags
997             )
998            
999 class Home(object):
1000     """ contains the parameters describing an agent's home location as returned in login_response['home'] """
1001
1002     def __init__(self, params):
1003         """ initialize the Home object by parsing the data passed in """
1004
1005         # eval(params) would be nice, but fails to parse the string the way one thinks it might
1006         items =  params.split(', \'')
1007
1008         # this creates:
1009         #   self.region_handle
1010         #   self.look_at
1011         #   self.position
1012         for i in items:
1013             i = re.sub(r'[\"\{}\'"]', '', i)
1014             i = i.split(':')
1015             setattr(self, i[0], eval(re.sub('r', '', i[1])))
1016
1017         self.global_x = self.region_handle[0]
1018         self.global_y = self.region_handle[1]
1019
1020         self.local_x = self.position[0]
1021         self.local_y = self.position[1]
1022         self.local_z = self.position[2]
1023
1024 """
1025 Contributors can be viewed at:
1026 http://svn.secondlife.com/svn/linden/projects/2008/pyogp/CONTRIBUTORS.txt
1027
1028 $LicenseInfo:firstyear=2008&license=apachev2$
1029
1030 Copyright 2009, Linden Research, Inc.
1031
1032 Licensed under the Apache License, Version 2.0 (the "License").
1033 You may obtain a copy of the License at:
1034     http://www.apache.org/licenses/LICENSE-2.0
1035 or in
1036     http://svn.secondlife.com/svn/linden/projects/2008/pyogp/LICENSE.txt
1037
1038 $/LicenseInfo$
1039 """
Note: See TracBrowser for help on using the browser.